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

rust-http-playback-proxy

v0.8.0

Published

HTTP traffic recording and playback proxy with precise timing control for PageSpeed optimization and performance testing

Readme

HTTP Playback Proxy - TypeScript/Node.js Wrapper

TypeScript/Node.js wrapper for the HTTP Playback Proxy Rust binary.

Installation

npm install http-playback-proxy

Usage

Recording Mode

import { startRecording } from 'http-playback-proxy';

async function record() {
  // Start recording proxy (with entry URL)
  const proxy = await startRecording({
    entryUrl: 'https://example.com',
    port: 8080,
    deviceType: 'mobile',
    inventoryDir: './inventory',
  });

  console.log(`Recording proxy started on port ${proxy.port}`);

  // Do your HTTP requests through the proxy (e.g., with a browser)
  // ...

  // Stop recording after some time (this saves the inventory)
  setTimeout(async () => {
    await proxy.stop();

    // Load the recorded inventory
    const inventory = await proxy.getInventory();
    console.log(`Recorded ${inventory.resources.length} resources`);
  }, 10000);
}

// Example: Start recording without entry URL (manual browsing)
async function recordWithoutEntryURL() {
  // All options are optional - uses defaults
  const proxy = await startRecording({});

  console.log(`Recording proxy started on port ${proxy.port}`);
  console.log('Configure your browser to use proxy 127.0.0.1:8080');
  console.log('Then browse to any website...');

  // Stop after some time
  setTimeout(async () => {
    await proxy.stop();
  }, 30000);
}

record().catch(console.error);

Playback Mode

import { startPlayback } from 'http-playback-proxy';

async function playback() {
  // Start playback proxy
  const proxy = await startPlayback({
    port: 8080,
    inventoryDir: './inventory',
    fullThrottle: false, // Set true to disable timing control (TTFB and transfer speed)
    passthrough: true,   // Forward unmatched requests to real servers instead of 404
  });

  console.log(`Playback proxy started on port ${proxy.port}`);

  // Do your HTTP requests through the proxy
  // The proxy will replay the recorded responses with accurate timing
  // ...

  // Stop playback after some time
  setTimeout(async () => {
    await proxy.stop();
  }, 10000);
}

playback().catch(console.error);

Working with Inventory

import { loadInventory, getResourceContentPath } from 'http-playback-proxy';

async function analyzeInventory() {
  // Load inventory
  const inventory = await loadInventory('./inventory/index.json');

  // Iterate through resources
  for (const [i, resource] of inventory.resources.entries()) {
    console.log(`Resource ${i}: ${resource.method} ${resource.url}`);
    console.log(`  TTFB: ${resource.ttfbMs} ms`);
    if (resource.statusCode) {
      console.log(`  Status: ${resource.statusCode}`);
    }

    // Get content file path
    if (resource.contentFilePath) {
      const contentPath = getResourceContentPath('./inventory', resource);
      console.log(`  Content: ${contentPath}`);
    }
  }
}

analyzeInventory().catch(console.error);

API Reference

Types

RecordingOptions

interface RecordingOptions {
  entryUrl?: string;       // Optional: Entry URL to start recording from
  port?: number;           // Optional: Port to listen on (default: 8080, will auto-search)
  deviceType?: DeviceType; // Optional: 'desktop' or 'mobile' (default: 'mobile')
  inventoryDir?: string;   // Optional: Directory to save inventory (default: './inventory')
}

PlaybackOptions

interface PlaybackOptions {
  port?: number;           // Optional: Port to listen on (default: 8080, will auto-search)
  inventoryDir?: string;   // Optional: Directory containing inventory (default: './inventory')
  fullThrottle?: boolean;  // Optional: Disable timing control (TTFB and transfer speed) for fastest playback
  passthrough?: boolean;   // Optional: Forward unmatched requests to real servers instead of 404
}

Inventory

interface Inventory {
  entryUrl?: string;
  deviceType?: DeviceType;
  resources: Resource[];
}

Resource

interface Resource {
  method: string;
  url: string;
  ttfbMs: number;
  mbps?: number;
  statusCode?: number;
  errorMessage?: string;
  rawHeaders?: Record<string, string>;
  contentEncoding?: ContentEncodingType; // 'gzip' | 'compress' | 'deflate' | 'br' | 'zstd' | 'identity'
  contentTypeMime?: string;
  contentCharset?: string;
  contentFilePath?: string;
  contentUtf8?: string;
  contentBase64?: string;
  minify?: boolean;
}

Functions

startRecording(options: RecordingOptions): Promise<Proxy>

Starts a recording proxy.

startPlayback(options: PlaybackOptions): Promise<Proxy>

Starts a playback proxy.

Proxy.stop(): Promise<void>

Stops the proxy gracefully.

Proxy.isRunning(): boolean

Checks if the proxy is still running.

Proxy.wait(): Promise<void>

Waits for the proxy to exit.

Proxy.getInventory(): Promise<Inventory>

Loads the inventory for this proxy.

loadInventory(path: string): Promise<Inventory>

Loads an inventory from a JSON file.

saveInventory(path: string, inventory: Inventory): Promise<void>

Saves an inventory to a JSON file.

getResourceContentPath(inventoryDir: string, resource: Resource): string

Returns the full path to a resource's content file.

getInventoryPath(inventoryDir: string): string

Returns the path to the index.json file.

ensureBinary(): Promise<void>

Ensures the binary is available, downloading if necessary.

Binary Distribution

How Binaries are Located

The TypeScript wrapper searches for binaries in the following order:

  1. Package directory (node_modules/http-playback-proxy/bin/<platform>/) - Production
    • Binaries are bundled with the npm package via GitHub Actions
    • Included in the files field of package.json
  2. Cache directory (~/.cache/http-playback-proxy/bin/<platform>/) - Fallback
    • Used if package binaries are not available
    • Automatically downloaded on first use
    • Can be customized via HTTP_PLAYBACK_PROXY_CACHE_DIR environment variable

Package Contents

The npm package includes:

  • dist/ - Compiled TypeScript code
  • bin/ - Prebuilt binaries for all supported platforms
  • README.md - Documentation

First-Time Usage

The wrapper automatically ensures the binary is available:

import { startRecording } from 'http-playback-proxy';

async function main() {
  // Automatically uses packaged binary or downloads if needed
  const proxy = await startRecording({});
  // ...
}

You can also explicitly ensure the binary is available:

import { ensureBinary } from 'http-playback-proxy';

await ensureBinary();

checkBinaryExists(): boolean

Checks if the binary exists locally.

downloadBinary(version?: string): Promise<void>

Downloads a specific version of the binary from GitHub Releases.

Environment Variables

  • HTTP_PLAYBACK_PROXY_CACHE_DIR: Custom cache directory for downloaded binaries
  • XDG_CACHE_HOME: XDG cache directory (fallback)

If neither is set, binaries are cached in ~/.cache/http-playback-proxy.

License

MIT