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

wire-mesh

v3.6.5

Published

A self-hostable, no-cloud LAN counterpart to `@exadev/wire-mesh-cloudflare-hub`: the same relay role (device discovery over gossip, pairing `relay-connect` initiators with their target, forwarding `relay-data` both ways), served by a plain Node process in

Readme

wire-mesh

A self-hostable, no-cloud LAN counterpart to @exadev/wire-mesh-cloudflare-hub: the same relay role (device discovery over gossip, pairing relay-connect initiators with their target, forwarding relay-data both ways), served by a plain Node process instead of a Cloudflare Durable Object. Run it on any machine already reachable on the network — a home server, a laptop on the same Wi-Fi, a container on a LAN — with no Cloudflare account, no deployment step, and no cloud dependency at all.

Why this exists

cloudflare-hub is the always-on, public reference deployment. It needs an account and a deployment pipeline, and it puts every relayed byte through Cloudflare's network. wire-mesh is for the opposite case: two devices on the same LAN (or reachable over a VPN/tailnet) that want a relay neither of them has to pay for or deploy anywhere — just npx wire-mesh on whichever machine is already running.

How it maps onto core's ports

The relay/pairing/gossip-registry domain logic itself is not duplicated here — it is wire-mesh-core's ./domain/relay-hub export, the same module cloudflare-hub consumes, proven transport-agnostic by running against core's TCP adapter, cloudflare-hub's WebSocket adapter, and this package's own adapter alike. This package supplies exactly one new thing: a Connection/Transport port implementation over the ws npm package (src/adapters/node-websocket-transport.ts) — the same one-CBOR-frame-per-binary-message convention as the hub's and web-console's own WebSocket adapters, but driven through ws's idiomatic Node EventEmitter API (.on, not .addEventListener) rather than the platform WebSocket those two wrap. Unlike the browser adapter, whose listen() always rejects (a browser tab can never accept inbound connections), and unlike cloudflare-hub's adapter, which has no listen() at all (ingress arrives through the Durable Object's own upgrade handling), this adapter's listen() is a real implementation: this package's entire reason to exist is being reachable on a LAN.

src/server.ts is the CLI entrypoint: it wires createRelayHub() over createNodeWebSocketTransport(). The same listener answers a browser too: GET /health returns a small JSON health response (the same shape cloudflare-hub's own healthResponse() returns), and every other request is served from @exadev/wire-mesh-web-console's own built static output — copied into this package's dist/web-console at build time (scripts/copy-web-console-dist.mjs, run as the second half of the _build script), so a self-hosted node has somewhere to point a browser at, not just other wire-mesh peers (wire-mesh#184). An extension-less path that doesn't match a real file falls back to index.html, so the console's own client-side routes still resolve.

Running it

npx wire-mesh                       # binds 0.0.0.0:8787, plain ws:// + http://
npx wire-mesh --bind 0.0.0.0:9000    # a different port
npx wire-mesh --bind 127.0.0.1:8787  # loopback only, if that's genuinely what you want
npx wire-mesh --tls-cert cert.pem --tls-key key.pem   # wss:// + https://, cert/key must be given together

or, installed as a dependency:

pnpm add wire-mesh
wire-mesh --bind 0.0.0.0:8787

Every flag the CLI accepts, exactly as wire-mesh --help prints it. An unknown flag, a flag missing its value, or a malformed --bind address exits non-zero with a message on stderr rather than starting a node; the --tls-cert/--tls-key pairing is enforced the same way. The parser, this text, and the help output all come from one flag table in src/cli-options.ts, and a test fails if this block stops matching it.

Usage: wire-mesh [options]

Options:
      --bind <host:port>                Address to listen on. Port 0 asks the OS for a free port. Use 127.0.0.1:8787 to accept local connections only. (default: 0.0.0.0:8787)
      --tls-cert <path>                 PEM certificate file, to serve wss:// and https://. Requires --tls-key.
      --tls-key <path>                  PEM private key file for --tls-cert. Requires --tls-cert.
      --mailbox-dir <path>              Directory to hold other devices' logs in while they are offline, which makes this node an announcer as well as a relay. Bounded by fixed limits. Off unless given.
      --webtransport <host:port>        Also serve WebTransport on this UDP address, with a self-signed certificate renewed on a schedule that a browser accepts by pinning its hash, and print the address to give a console. Needs the optional @fails-components/webtransport dependency. Off unless given.
      --state-dir <path>                Directory to keep the WebTransport certificates in, private keys included, so a restart serves the same certificates and the addresses already handed out stay valid. Created readable by this user only. Without it the certificates live in memory and a restart starts a new set. Needs --webtransport.
      --certificate-lifetime <seconds>  How long each WebTransport certificate is valid, in seconds. The node rotates to the next certificate every half lifetime, ending the sessions open on the server it replaces, so a shorter lifetime rotates more often. At most the WebTransport limit for a pinned certificate, which is also the default. Needs --webtransport.
  -h, --help                            Print this help and exit.
  -v, --version                         Print the version and exit.

Default bind address is 0.0.0.0:8787, not loopback. Unlike a typical dev-server tool, whose loopback-only default assumes only the machine itself needs to reach it, this package's whole purpose is LAN reachability — a phone on the same network, a laptop in the next room. --bind overrides the address if you want to restrict it (loopback-only, a specific interface, a different port).

Point a browser at the bound address to reach the console, or check its health directly:

curl http://<host>:8787/health
# {"ok":true,"node":"wire-mesh","roles":["relay"]}

With --webtransport <host:port> the node also serves WebTransport on a UDP port, for a browser that has no way to trust a certificate for this node's LAN address. The node mints self-signed ECDSA certificates, each valid for under two weeks, and prints an address of the form https://<host>:<port>#sha256=<hash>[,<hash>...] for each address of this machine a client can reach (when bound to a wildcard, one per non-internal IPv4 address). Give one of them to a console (the Node field takes it as typed): a browser accepts the certificate whose hash matches any in the list, with no certificate authority and no internet.

The list holds the certificate served now and the ones that will replace it. The node rotates to the next certificate every half lifetime, by replacing its WebTransport server, so open sessions on that server end and a console reconnects, and an address stays usable for about three of those periods after it was copied, and a console that reconnects is told the current list by the node, so one that keeps reconnecting never needs a new address. Without --state-dir the certificates live in memory, so a restart starts a new set and every address already handed out stops working; with it the certificates, private keys included, are kept in that directory (created readable by this user only) and a restart resumes them. The server is the optional @fails-components/webtransport dependency with its native binary; where it is not installed the flag fails at startup rather than serving without it. Safari, on macOS and iOS, cannot open a stream to the released server until the HTTP/3 library it is built on sends WebTransport session flow-control credit (fails-components/webtransport#490), so a Safari console reports that and needs a wss:// address instead. The prebuilt binary needs glibc 2.38 or newer on Linux, and its install script has to be allowed to run: a package manager that skips install scripts, or holds them for approval as recent npm does (npm warn allow-scripts, then npm approve-scripts), leaves the binary missing.

With --mailbox-dir the node also serves the announcer role and /health says so ("roles":["relay","announcer"]). It then holds other devices' core/data logs in that directory while those devices are offline, so a peer that reconnects catches up from the node. Only the device that owns a log can write to it: the node takes a log's entries from the connection whose verified advert names that device and from no one else. Any client that has gossiped an advert can read a log. Entries are opaque bytes to the node, and the applications that write them sign and encrypt their own. The number of logs, the bytes in each and the size of an entry are bounded (src/mailbox-limits.ts), and a frame that would exceed a bound is refused whole; nothing is evicted, so a full log stays full until the directory is cleared.

TLS (wss:///https://) is opt-in via --tls-cert/--tls-key, both required together — this package generates no certificate of its own, so bring your own (a real one from a CA, or a self-signed one for a LAN). This matters specifically for wire-mesh#182/#183: an https://-served PWA (mesh.exadev.io) can only reach a plain ws:// node if it's literally on localhost of the same machine, since browsers block mixed-content WebSocket connections from a secure page to an insecure one — a self-hosted node on someone's LAN, addressed by its own IP, needs to answer wss:// for that PWA to reach it at all. How a LAN node is meant to be reached from the HTTPS-served console, and why a certificate is not the whole answer, is worked through in docs/lan-certificates.md.

Security note: this package adds no access control beyond the protocol's own capability tokens (core/exec/core/management verbs gated by a signed capability, same as any other wire-mesh node) — anyone who can reach the bound address can gossip, relay-connect, and relay-data through it. Bind to a trusted network (a home LAN, a VPN/tailnet) rather than a public interface unless the capability-token story for whatever you're relaying is one you're comfortable exposing to the open internet.

What is real versus deferred

Real and tested: the Node WebSocket transport adapter (hostile-input behaviour included — undecodable bytes and non-binary messages close the connection; a decodable-but-schema-invalid frame drops without disconnecting), a real loopback round trip (listen("127.0.0.1:0", ...) + connect(...) against the actual bound address, proving the OS-assigned-port path works), a full relay path verified end to end against real sockets — two genuine WebSocket clients exchanging gossip, relay-connect, relay-inbound, and bidirectional relay-data through a real listener, the in-suite analogue of cloudflare-hub/scripts/live-check.mjs for a runtime (plain Node) with no dev-server lifecycle constraints to work around — a real TLS handshake terminating a genuine wss:// WebSocket connection and an https:// request against a self-signed fixture certificate, and the console's own static-file routing (root, nested assets, the service worker's required Service-Worker-Allowed header, the client-side-route fallback to index.html, and a 404 for a genuinely missing file) against a real HTTP server.

Deferred deliberately, matching cloudflare-hub's own list:

  • The announcer role (discovery.cddl's mailboxes) needs a Storage port adapter; none is wired up here yet.

Type environment

src/ is plain Node code, typechecked against @types/node and @types/ws — no DOM lib, no Worker types, matching wire-mesh-core's own type environment exactly (the closest precedent this package's scaffolding mirrors).