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

ohttp-ts

v0.3.0

Published

Oblivious HTTP (RFC 9458) implementation with chunked extension support

Readme

ohttp-ts

NPM License

TypeScript implementation of Oblivious HTTP (RFC 9458) with streaming support.

Features

Installation

npm install ohttp-ts hpke

Or via CDN (no install):

import { KeyConfig, OHTTPClient, OHTTPServer } from "https://esm.sh/ohttp-ts";
import { CipherSuite, KEM_DHKEM_X25519_HKDF_SHA256, KDF_HKDF_SHA256, AEAD_AES_128_GCM } from "https://esm.sh/hpke";

Quick Start

import { CipherSuite, KEM_DHKEM_X25519_HKDF_SHA256, KDF_HKDF_SHA256, AEAD_AES_128_GCM } from "hpke";
import { KeyConfig, OHTTPClient, OHTTPServer, KdfId, AeadId } from "ohttp-ts";

// Gateway: generate key configuration
const suite = new CipherSuite(KEM_DHKEM_X25519_HKDF_SHA256, KDF_HKDF_SHA256, AEAD_AES_128_GCM);
const keyConfig = await KeyConfig.generate(suite, 0x01, [
  { kdfId: KdfId.HKDF_SHA256, aeadId: AeadId.AES_128_GCM },
]);
const gateway = new OHTTPServer([keyConfig]);

// Client: fetch and parse gateway's public key
const publicKeyBytes = KeyConfig.serialize(keyConfig);
const clientKeyConfig = KeyConfig.parse(publicKeyBytes);
const client = new OHTTPClient(suite, clientKeyConfig);

// Client: encapsulate HTTP request
const httpRequest = new Request("https://target.example/api", {
  method: "POST",
  body: JSON.stringify({ data: "sensitive" }),
});
const { init, context } = await client.encapsulateRequest(httpRequest);

// Send to relay
const relayResponse = await fetch("https://relay.example/ohttp", init);

// Gateway: decapsulate request (received from relay)
const { request: innerRequest, context: serverContext } = await gateway.decapsulateRequest(relayRequest);
// relayRequest is what the relay receives and forwards to the gateway
// innerRequest is the original Request object

// Gateway: encapsulate response
const httpResponse = new Response(JSON.stringify({ result: "ok" }), { status: 200 });
const encapsulatedResponse = await serverContext.encapsulateResponse(httpResponse);

// Client: decapsulate response
const innerResponse = await context.decapsulateResponse(relayResponse);
// innerResponse is the original Response object

Protocol Flow

+---------+       +-------+       +---------+    +--------+
| Client  |       | Relay |       | Gateway |    | Target |
+---------+       +-------+       +---------+    +--------+
     |                |                |             |
     | Encapsulated   |                |             |
     | Request        |                |             |
     +--------------->| Forward        |             |
     |                +--------------->| Decrypt &   |
     |                |                | Forward     |
     |                |                +------------>|
     |                |                |             |
     |                |                |<------------+
     |                |                | Encrypt     |
     |                |<---------------+ Response    |
     |<---------------+                |             |
     | Decapsulated   |                |             |
     | Response       |                |             |

Binary HTTP

OHTTP encapsulates Binary HTTP (RFC 9292) messages. The high-level API (encapsulateRequest, decapsulateRequest, etc.) handles encoding automatically.

For advanced use cases, the low-level bytes API is also available:

// Low-level API: work with raw Binary HTTP bytes
const { encapsulatedRequest, context } = await client.encapsulate(binaryHttpBytes);
const { request: binaryBytes, context: serverCtx } = await gateway.decapsulate(encapsulatedRequest);

See examples/bhttp.example.ts for a complete example.

Chunked OHTTP (Streaming)

Use chunked OHTTP when:

  • Large payloads (>1MB) that would exceed memory limits
  • Incremental sources - data arrives over time (file uploads, network streams)
  • Early processing - need to start processing before full body arrives
  • Memory-constrained - Workers (128MB), mobile, edge

Use normal OHTTP when:

  • Small payloads (<100KB)
  • Need full body - JSON.parse(), image processing, etc.
  • Latency-sensitive - streaming has async overhead
// Normal: ~3x payload memory, faster for in-memory data
const client = new OHTTPClient(suite, keyConfig);

// Chunked: ~64KB constant memory, better for large/streaming data
const client = new ChunkedOHTTPClient(suite, keyConfig);

For streaming large requests/responses, use ChunkedOHTTPClient/ChunkedOHTTPServer:

import { ChunkedOHTTPClient, ChunkedOHTTPServer } from "ohttp-ts";

// Setup (same key configuration as above)
const gateway = new ChunkedOHTTPServer([keyConfig]);
const client = new ChunkedOHTTPClient(suite, keyConfig);

// Client: encapsulate streaming request
const streamingRequest = new Request("https://target.example/upload", {
  method: "POST",
  body: largeReadableStream,
  // @ts-expect-error - required for streaming bodies in Node.js
  duplex: "half",
});
const { init, context } = await client.encapsulateRequest(streamingRequest);

// Send to relay (init includes duplex: "half" for streaming)
const relayResponse = await fetch("https://relay.example/ohttp", init);

// Gateway: decapsulate (body streams through)
// relayRequest is what the relay receives and forwards to the gateway
const { request: innerRequest, context: serverContext } =
  await gateway.decapsulateRequest(relayRequest);

// Process body incrementally
for await (const chunk of innerRequest.body!) {
  // Process chunk without buffering entire body
}

// Gateway: stream response back
const streamingResponse = new Response(responseStream, { status: 200 });
const encapsulatedResponse = await serverContext.encapsulateResponse(streamingResponse);

// Client: decapsulate and consume streaming response
const finalResponse = await context.decapsulateResponse(relayResponse);
for await (const chunk of finalResponse.body!) {
  // Process chunk as it arrives
}

Note: Request/Response bodies stream through without full buffering. Only the BHTTP preamble (method/status, headers) is buffered before the body can flow.

For the low-level bytes API, see examples/chunked.example.ts.

Examples

| Example | Description | |---------|-------------| | ohttp.example.ts | Basic OHTTP round-trip | | chunked-http.example.ts | Streaming Request/Response API | | chunked.example.ts | Low-level bytes API | | bhttp.example.ts | Request/Response API (non-streaming) | | mlkem.example.ts | Post-quantum with ML-KEM-768 |

Post-Quantum Support

For post-quantum key encapsulation (ML-KEM), use @panva/hpke-noble:

npm install @panva/hpke-noble
import { CipherSuite } from "hpke";
import { KEM_ML_KEM_768, KDF_HKDF_SHA256, AEAD_AES_128_GCM } from "@panva/hpke-noble";

const suite = new CipherSuite(KEM_ML_KEM_768, KDF_HKDF_SHA256, AEAD_AES_128_GCM);
// Use with KeyConfig.generate(), OHTTPClient, OHTTPServer as usual

Security Considerations

Not audited. Use at your own risk.

  • Replay protection is out of scope (RFC 9458 Section 6.5)
  • Decryption errors are opaque to prevent oracle attacks

License

MIT