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

@xof/quic

v1.0.0

Published

Low-level QUIC v1/v2 and HTTP/3 protocol stack for Node.js with TLS 1.3, recovery, streams, QPACK, diagnostics, QLog, and interoperability tooling.

Readme

@xof/quic

npm version downloads

Low-level QUIC v1/v2 and HTTP/3 protocol stack for Node.js.

@xof/quic exposes the transport layer directly, plus an HTTP/3 layer built on top of the same QUIC connection. The package is CommonJS-first and has no runtime dependencies.

Requirements

  • Node.js >= 20
  • UDP access to the target host/port

Installation

npm install @xof/quic

Import

The package uses CommonJS:

const {
  QUICClient,
  QUICServer,
  QUICConnection,
  HTTP3Connection,
  attachHTTP3
} = require('@xof/quic');

QUIC server (PSK mode)

QUICServer listens on UDP. By default it uses QUIC v1, ALPN xof, and the built-in PSK value unless psk is provided.

'use strict';

const { QUICServer } = require('@xof/quic');

const server = new QUICServer({
  host: '127.0.0.1',
  port: 4433,
  psk: process.env.XOF_QUIC_PSK || 'xof-quic-local-dev-psk-change-me',
  identity: process.env.XOF_QUIC_IDENTITY || 'xof',
  alpn: 'xof'
});

server.on('listening', ({ address, port }) => {
  console.log(`QUIC server listening on ${address}:${port}`);
});

server.on('connection', conn => {
  console.log('QUIC connection established');

  conn.on('data', (data, connection, streamId) => {
    console.log(`stream ${streamId}:`, data.toString());

    connection.write(data, {
      streamId,
      fin: true
    });
  });

  conn.on('error', err => {
    console.error('[connection error]', err);
  });

  conn.on('close', info => {
    console.log('QUIC connection closed:', info);
  });
});

server.on('clientError', err => {
  console.error('[clientError]', err);
});

server.on('error', err => {
  console.error('[server error]', err);
});

server.listen().catch(err => {
  console.error(err);
  process.exitCode = 1;
});

QUIC client

connect() resolves to a QUICConnection.

'use strict';

const { QUICClient } = require('@xof/quic');

(async () => {
  const client = new QUICClient({
    host: '127.0.0.1',
    port: 4433,
    psk: process.env.XOF_QUIC_PSK || 'xof-quic-local-dev-psk-change-me',
    identity: process.env.XOF_QUIC_IDENTITY || 'xof',
    alpn: 'xof',
    handshakeTimeout: 15000
  });

  try {
    const conn = await client.connect();

    conn.on('ready', () => {
      console.log('QUIC ready');
    });

    conn.on('data', (data, connection, streamId) => {
      console.log(`stream ${streamId}:`, data.toString());
    });

    conn.on('error', err => {
      console.error('[connection error]', err);
    });

    const stream = conn.openStream();
    stream.end(Buffer.from('hello from xof-quic'));
  } catch (err) {
    console.error('[client error]', err);
  }

  setTimeout(() => {
    client.close();
  }, 500);
})();

Stream API

After a connection is open, QUICConnection exposes:

const stream = conn.openStream();

stream.id;
stream.write(Buffer.from('part 1'));
stream.end(Buffer.from('part 2'));
stream.reset(0);

For a unidirectional stream:

const stream = conn.openUnidirectionalStream();
stream.write(Buffer.from('hello'));
stream.end();
stream.reset(0);

The connection also provides direct transport helpers:

conn.write(Buffer.from('hello'), {
  streamId: 0n,
  fin: true
});

conn.sendDatagram(Buffer.from('ping'));
conn.resetStream(0n, 0);
conn.updateKeys();
conn.close();

sendDatagram() requires the peer to have negotiated a non-zero maxDatagramFrameSize.


HTTP/3

HTTP/3 is attached to an already-established QUICConnection with attachHTTP3().

The repository's HTTP/3 server follows this exact pattern:

'use strict';

const { QUICServer, attachHTTP3 } = require('@xof/quic');

const server = new QUICServer({
  host: '0.0.0.0',
  port: 4433,
  alpn: 'h3',
  noPsk: true,
  cert: './certs/cert.pem',
  key: './certs/key.pem'
});

server.on('connection', conn => {
  const h3 = attachHTTP3(conn);

  h3.on('request', req => {
    const chunks = [];

    req.on('data', chunk => {
      chunks.push(chunk);
    });

    req.on('end', () => {
      const body = Buffer.concat(chunks);

      req.respond(200, {
        'content-type': 'application/octet-stream'
      }, body);
    });
  });

  h3.on('streamError', info => {
    console.error('[HTTP/3 stream error]', info.error);
  });
});

server.listen().catch(err => {
  console.error(err);
  process.exitCode = 1;
});

HTTP/3 client

h3.request() returns a request object. The request exposes a response Promise that resolves after the complete response body has been received.

'use strict';

const fs = require('node:fs');
const { QUICClient, attachHTTP3 } = require('@xof/quic');

(async () => {
  const client = new QUICClient({
    host: '127.0.0.1',
    port: 4433,
    alpn: 'h3',
    noPsk: true,
    servername: 'localhost',
    ca: fs.readFileSync('./certs/cert.pem'),
    rejectUnauthorized: true,
    handshakeTimeout: 15000
  });

  try {
    const conn = await client.connect();
    const h3 = attachHTTP3(conn);

    const req = h3.request({
      method: 'POST',
      scheme: 'https',
      authority: 'localhost',
      path: '/',
      headers: {
        'content-type': 'application/octet-stream'
      },
      body: Buffer.from('hello from HTTP/3')
    });

    const res = await req.response;

    console.log('status:', res.status);
    console.log('headers:', res.headers);
    console.log('trailers:', res.trailers);
    console.log('body:', res.body);
    console.log('bodyBytes:', res.bodyBytes);
    console.log('text:', res.text());
  } finally {
    client.close();
  }
})();

HTTP/3 request methods

The current request() options are:

h3.request({
  method: 'GET',
  scheme: 'https',
  authority: 'localhost',
  path: '/',
  headers: {},
  body: null
});

For requests with a body, the value can be a Buffer or another value accepted by Buffer.from().

The returned request object provides:

req.id;
req.response;
req.write(data);
req.end(data);
req.cancel();
req.on(event, handler);

The response Promise resolves to an object containing:

{
  id,
  status,
  headers,
  trailers,
  body,       // Buffer
  bodyBytes,
  text(encoding),
  json()
}

HTTP/3 connections also emit ready, settings, goaway, headers, data, trailers, end, unknownFrame, streamError, and close events according to the received protocol state.

HTTP/3 response

On the server, the request object is a readable stream with HTTP/3 request metadata:

h3.on('request', req => {
  console.log(req.method);
  console.log(req.path);
  console.log(req.authority);
  console.log(req.scheme);
  console.log(req.headers);

  req.on('data', chunk => {
    console.log(chunk);
  });

  req.on('end', () => {
    req.respond(200, {
      'content-type': 'application/json'
    }, JSON.stringify({
      creator: 'Xyraa Kyxzz',
      status: true,
      data: {
        message: 'Hello from HTTP/3'
      }
    }));
  });
});

respond(status, headers, body) sends the response HEADERS and optional DATA, then finishes the stream.


Certificate mode

QUICServer enters certificate mode when noPsk: true or a certificate is configured.

const { QUICServer } = require('@xof/quic');

const server = new QUICServer({
  host: '0.0.0.0',
  port: 4433,
  alpn: 'h3',
  noPsk: true,
  cert: './certs/cert.pem',
  key: './certs/key.pem'
});

The client-side certificate settings are provided through ca, servername, and rejectUnauthorized.

const client = new QUICClient({
  host: '127.0.0.1',
  port: 4433,
  alpn: 'h3',
  noPsk: true,
  servername: 'localhost',
  ca: fs.readFileSync('./certs/cert.pem'),
  rejectUnauthorized: true
});

For the bundled development certificate, servername: 'localhost' matches the certificate subject.


Handshake timeout and diagnostics

Both client and server use an explicit handshake timeout. The default is 15000 milliseconds and can be changed with handshakeTimeout.

const client = new QUICClient({
  host: 'example.com',
  port: 443,
  handshakeTimeout: 10000
});

client.on('timeout', err => {
  console.error(err.code);
  console.error(err.diagnosis);
});

DNS failures are reported separately from handshake timeouts. A timeout can include TCP reachability information in error.diagnosis, but TCP reachability does not prove that QUIC or HTTP/3 is available.

Connection diagnostics are also available after connection setup:

console.log(conn.getDiagnostics());
console.log(conn.getFingerprint());
console.log(conn.getPath());
console.log(conn.getPeerTransportParameters());

QLog and packet diagnostics

QLog can be enabled from the connection or through the qlog server/client option:

conn.enableQLog();

Packet-level forensic details are disabled by default. Enable them explicitly when detailed packet inspection is required:

XOF_PACKET_DETAILS=1 node your-script.js

The repository also contains quic-detail.txt used by the diagnostic test script.


Repository server

The repository root contains a complete HTTP/3 development server in server.js. It uses the bundled development certificate and listens on UDP port 4433.

node server.js

Or with Node watch mode:

npm run dev

A matching HTTP/3 client can use the certificate-mode example shown above.

Examples

The repository also contains standalone client/server example files under examples/. The current HTTP/3 implementation itself is exposed through attachHTTP3() as shown in this README; a raw QUIC connection does not emit HTTP request/response objects by itself.

Testing

The package test script points to the diagnostic runner and requires a target host. Pass the host and optional port after --:

npm test -- example.com 443

The same diagnostic script at the repository root can be run directly with a target host and optional port:

node test.js <host> [port]

Example:

node test.js example.com 443

It writes packet/TLS diagnostic output to quic-detail.txt.

Public API

The CommonJS entry point exports the protocol primitives from lib/index.js, including:

  • QUICClient
  • QUICServer
  • QUICConnection
  • HTTP3Connection
  • attachHTTP3
  • QUIC v1/v2 constants and packet/frame helpers
  • TLS 1.3, packet protection, HKDF, Retry, and QPACK helpers
  • recovery, congestion-control, flow-control, path, token, and QLog helpers
  • QUICError, QUICTransportError, QUICProtocolError, and QUICCryptoError
const xquic = require('@xof/quic');

console.log(Object.keys(xquic));

Changelog

Changelog

License

MIT © XOF