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

@frontierengineer/link-client

v0.2.1

Published

Auditable, multi-uplink secure-connection client for the Link relay model: Noise (NKpsk0) sealed channel + SPAKE2 (RFC 9382) short-code pairing, end-to-end encrypted over content-blind relays.

Readme

@frontierengineer/link-client

An auditable secure-connection library for the Link relay model: reach a NAT'd host from anywhere, through relays you don't have to trust, with no accounts — end-to-end encrypted, authenticated by a per-host credential.

One library is both ends of the model: a host uses it to register with its relays and accept clients; a client uses it to connect. The relay only ever moves ciphertext.

  • First-pair: SPAKE2 (RFC 9382) so a hand-typed 6-character code is safe over an untrusted relay — proven against the RFC's own test vectors.
  • Reconnect: Noise NKpsk0 (X25519 + ChaCha20-Poly1305 + SHA-256) — proven byte-for-byte against the published Noise vectors.
  • Primitives adopted, never hand-rolled: @noble/curves, @noble/ciphers, @noble/hashes.

The model in one paragraph

The host opens no inbound port. It holds an outbound control socket to each of its relays and registers a high-entropy address (signed, so it can't be squatted). A client connects outbound to a relay, is introduced to the host, the relay splices their two sockets, and the two endpoints run an end-to-end handshake the relay cannot see inside. Two cleanly separated guards: the relay guards introduction (you resolve an address to be spliced — but that's just a phone ring), and the end-to-end credential (a pinned host key + device token) guards access. Register with several relays and the client fails over silently.

Quick start

import { connect, serveHost, generateHostIdentity } from '@frontierengineer/link-client';

// ── host ──
const identity = generateHostIdentity();           // persist identity.priv forever
const host = await serveHost({
  uplinks: ['wss://link.example.com/v1/link'],
  hostStatic: identity,
  onRequest: async (cmd) => handle(cmd),            // your app protocol
});
host.setPairingCode('K7P2QX');                      // open pairing; show host.address + code out of band

// ── a new client first-pairs (address from the QR, code typed/scanned with it) ──
const conn = await connect({
  uplinks: ['wss://link.example.com/v1/link'],
  address: host.address,                            // a QR/deep-link carries this
  code: 'K7P2QX',                                   // the short code keys SPAKE2 only
  onState: (s) => console.log('state:', s),
});
const credential = conn.credential;                 // PERSIST THIS (token + pinned key + address)
await conn.request({ hello: 'world' });             // sealed round-trip

// ── every later connection reuses the credential (the code was one-time) ──
const back = await connect({
  uplinks: ['wss://link.example.com/v1/link'],
  address: credential.address,
  credential,
});

connect() returns an already-secure, auto-reconnecting channel: it tries uplinks in order, relays through the first that works, and on any drop it re-handshakes — failing over across uplinks — using the credential. request() transparently rides out a reconnect.

Public API

connect(options) → Promise<Connection>

| option | meaning | |---|---| | uplinks: string[] | ordered relay URLs (failover order) | | address: string | the host's routing address (always required) | | code?: string | first pair: the short pairing code — keys SPAKE2 only, never sent to the relay | | credential?: DeviceCredential | reconnect: token + pinned host static key + address | | recoveryKey?: string | recover: a high-entropy secret to enroll a brand-new device from nothing | | onState? | connectingconnectedreconnecting→… ; terminal failed or revoked | | onRequest? | handle host-initiated requests on this client | | dial? | per-attempt timeouts (connectTimeoutMs, controlTimeoutMs) |

If the host has revoked this device, reconnect is refused with a typed DeviceRevokedError (and staterevoked): the managed connection stops retrying instead of spinning forever, so the app can forget the credential and re-pair.

Exactly one of code / credential / recoveryKey selects the mode. Connection: request(cmd), send(evt), onMessage(fn), credential, address, state, via, close().

serveHost(options) → Promise<Host>

Registers address (signed) with every uplink — N outbound control sockets, re-registered on reconnect/relay-restart — accepts introduced clients, runs the host handshake, and issues/verifies credentials.

| option | meaning | |---|---| | uplinks: string[] | relay URLs to register with | | hostStatic?: KeyPair | the host's static identity (defaults fresh — persist .priv). The routing address is always the commitment to the register key derived from this identity, base64url(SHA-256(key)), so it's spoof-proof and not configurable | | pairingCode?: string | open pairing immediately (else call setPairingCode) | | recoveryKey?: string | enable cold-start recovery with this high-entropy secret | | tokens?: TokenStore | a persisted device store (defaults empty) | | onRequest? / onConnect? / onLog? | app protocol / per-session hook / structured logs | | onUsage? | per-connection relay usage — a 0–1 fraction + throttled, or unlimited when the relay sets no quota; fires on Link's pushes and in answer to requestUsage() | | maxPairAttempts? | wrong-guess lockout per code (default 5) |

Host: address, hostStatic, hostStaticPub, tokens, sessions, registeredCount, setPairingCode(code|null), revoke(keyId), requestUsage(), stop().

Usage telemetry. Link reports usage only in relative terms — a fraction of the hourly allowance per connection, or unlimited — never absolute bytes and never its own limit config, so a byte budget can't be reverse-engineered. onUsage receives the usage of every connection this host owns; call requestUsage() to pull the current values on demand (the answer arrives on onUsage with the same shape as an unprompted push).

Security properties

  • First pair (short code). SPAKE2 turns the code into a strong shared key. Against an active man-in-the-middle (including a malicious relay) it is online-only: the attacker must commit to one guess per live attempt and learns nothing it can test offline, so the host's K-try lockout (default 5) caps success at attempts / charset^len. The lockout slot is reserved atomically at attempt entry, so concurrent attempts cannot bypass the cap (it doubles as a per-code concurrency cap on the unauthenticated handshake). Inside that channel the host sends its static public key and a 256-bit token; the client pins the key and stores the token. The code is never sent to the relay in any form.
  • Reconnect. Noise NKpsk0 authenticates the host by its pinned static key (a substituted key fails the AEAD) and the client by token possession (the token is the PSK; an unknown/revoked keyId is refused up front). Forward-secret per connection via the ephemeral-ephemeral DH.
  • Transport. Every frame is ChaCha20-Poly1305 under a per-direction, monotonic, never-transmitted nonce, so tamper, replay, reorder, and truncation are all rejected by the tag. There is no plaintext mode.
  • Rendezvous is address-only. The relay introduces by the high-entropy, signed address. The short code is never a relay lookup key (it would be brute-forceable) — only a SPAKE2 secret. So a malicious relay has nothing code-derived to attack. See docs/SECURITY.md.
  • Recovery. A high-entropy host secret lets a brand-new device enroll from nothing — the same flow as pairing, with the recovery key as the authenticator (concurrency-capped but intentionally not permanently locked out, so it can't be DoS'd).
  • Revocation. Drop the token (host.revoke(keyId)); the next reconnect finds no token and is refused. The host sends the refused device a typed signal, so the client surfaces a terminal DeviceRevokedError / revoked state (distinguishable from a transient drop) and stops retrying instead of looping forever. Revocation lives at the host, never at the relay.
  • Untrusted-relay hardening. Every socket is capped at the relay's 16 MiB frame ceiling; a tampered frame tears down and closes its socket; and a host bounds concurrent relay dial-backs per uplink (default 64) so a malicious relay can't drive unbounded outbound sockets.

Secrets at rest & delivery semantics

  • Tokens are secrets. A device token is the Noise PSK, so it cannot be stored hashed. Persist TokenStore (host) and the DeviceCredential (client) only through encryption you control; TokenStore.export()/import() is the seam for encrypt-at-rest.
  • request() is at-least-once. It retries across a reconnect, so a command whose response was lost may run twice on the host. Keep commands idempotent or carry an app-level idempotency key.

Architecture

Small, single-purpose modules — the whole crypto surface is meant to be read.

| module | role | |---|---| | src/crypto.ts | the only place primitives are bound (@noble/*); nonce/DH/HKDF encodings pinned | | src/noise.ts | a complete, minimal Noise engine (CipherState/SymmetricState/HandshakeState) | | src/spake2.ts | SPAKE2 over P-256 to RFC 9382 | | src/secureChannel.ts | the e2e layer: NKpsk0 reconnect, the sealed stream, the app session | | src/pairing.ts | SPAKE2 choreography, token store, lockout, the device credential | | src/registerAuth.ts | the signed-address registration (Ed25519, anti-squat) | | src/linkClient.ts | the multi-uplink transport over the relay wire (resolve/relay/failover, host register) | | src/index.ts | connect() / serveHost() | | src/pipe.ts · src/bytes.ts | the transport abstraction · framing + constant-time compare |

Tests — everything is checkable

npm install
npm test          # typecheck + unit (KAT) + the end-to-end self-test
  • test/secureChannel.test.ts — the Noise vectors (NN/NK/NKpsk0/XX/…) with byte-equality of every message + handshake hash + transport stream; the live reconnect handshake (substituted-key and wrong-token rejection); and the sealed stream's tamper/replay/reorder rejection.
  • test/pairing.test.ts — the four RFC 9382 SPAKE2 vectors; a proof that an active MITM who guesses the wrong code cannot derive the key; and the K-try lockout.
  • test/hardening.test.ts — the lockout TOCTOU mechanism (reserve() caps concurrent attempts at K) and the relay dial-back cap (a flood of relay requests opens at most N concurrent dial-backs, held through the handshake).
  • test/registerAuth.test.ts — signed-registration interop (@noble sign ↔ Node verify), the full anti-squat sequence against a real relay (unsigned refused, TOFU-pinned, wrong-key refused, same-key replaces, replay refused), and address-key binding (only the committed address registers; a squatter can't even craft a passable frame).
  • test/e2e.selftest.ts — spawns two real relay processes, registers a host with both, then: pair (address rendezvous + 6-char code) over uplink A → sealed request → pull usage (per-connection, unlimited)kill A, fail over to B by token reconnect → fresh token reconnect → revoke → reconnect refused with a typed DeviceRevokedError + terminal revoked state (no endless retry). Prints PASS/FAIL per stage; non-zero exit on any failure.

License

MIT — see LICENSE.