Skip to main content
Scramjet is a web proxy that intercepts and rewrites web content using Service Workers. This page explains how the core components work together to create a seamless proxying experience.

Core components

Scramjet consists of four main components that work in harmony:

Service Worker layer

The ScramjetServiceWorker is the heart of the proxy. It intercepts all network requests and performs the following tasks:
  • Intercepts fetch requests through the Service Worker API
  • Decodes proxied URLs to real destination URLs
  • Fetches content using a transport layer (via BareClient)
  • Rewrites responses (HTML, JS, CSS, etc.) before returning them
  • Manages cookies through an emulated cookie store
See ScramjetServiceWorker for details.

Client layer

The ScramjetClient runs in each proxied page (window or worker). It:
  • Intercepts DOM APIs and JavaScript APIs to rewrite URLs
  • Maintains a reference to the real URL of the current page
  • Handles navigation events and URL changes
  • Provides utilities for wrapping functions and trapping properties
  • Manages event callbacks to ensure proper interception
The client is injected into every HTML document via script tags in the <head> section.

Controller layer

The ScramjetController is the high-level API for initializing and managing Scramjet. It:
  • Stores configuration in IndexedDB for persistence
  • Provides methods to encode/decode URLs
  • Creates ScramjetFrame instances for easy iframe management
  • Dispatches global events like downloads
  • Allows runtime configuration updates
Typically, you initialize one controller per application:
const scramjet = new ScramjetController({
  prefix: "/scramjet/",
  flags: {
    strictRewrites: true,
    captureErrors: true
  }
});
await scramjet.init();

Frame abstraction

The ScramjetFrame provides a convenient API for managing proxied iframes:
  • Automatically handles frame name generation for internal tracking
  • Provides navigation methods (go(), back(), forward(), reload())
  • Exposes the ScramjetClient instance for the iframe
  • Dispatches navigation events you can listen to
Example usage:
const frame = scramjet.createFrame();
document.body.appendChild(frame.frame);

frame.addEventListener("urlchange", (e) => {
  console.log("URL changed to:", e.url);
});

frame.go("https://example.com");

Request flow

Here’s what happens when a proxied page makes a request:
1

URL encoding

The controller or client encodes the real URL into a proxied URL using the configured codec:
https://example.com → http://localhost/scramjet/https%3A%2F%2Fexample.com
2

Service Worker interception

The Service Worker’s fetch event handler intercepts the request. It checks if the URL matches the configured prefix.
3

URL decoding

The Service Worker decodes the proxied URL back to the real destination URL.
4

Fetch via transport

The Service Worker fetches the content using the BareClient transport layer, which handles the actual network request.
5

Response rewriting

Based on the content type, the Service Worker rewrites the response:
  • HTML: Injects Scramjet scripts and rewrites URLs in attributes
  • JavaScript: Rewrites using the oxc-based WASM rewriter
  • CSS: Rewrites url() references
  • Headers: Processes and rewrites cookies, CSP, etc.
6

Response delivery

The rewritten response is returned to the client, which renders it as if it came from the real site.

Data flow

Scramjet maintains several stores to track state across the proxy:

IndexedDB stores

Scramjet uses IndexedDB with the following object stores:
  • config: Stores the active ScramjetConfig
  • cookies: Stores the serialized cookie jar
  • redirectTrackers: Tracks redirect chains for referrer policy enforcement
  • referrerPolicies: Stores referrer policy data per URL
  • publicSuffixList: Caches the public suffix list for cookie domain validation

Message passing

The Service Worker and clients communicate via postMessage:
// Client → Service Worker messages (MessageC2W)
type MessageC2W =
  | { scramjet$type: "registerServiceWorker"; port: MessagePort; origin: string }
  | { scramjet$type: "cookie"; cookie: string; url: string }
  | { scramjet$type: "loadConfig"; config: ScramjetConfig };

// Service Worker → Client messages (MessageW2C)  
type MessageW2C =
  | { scramjet$type: "cookie"; cookie: string; url: string }
  | { scramjet$type: "download"; download: ScramjetDownload };
All messages include a scramjet$type field for routing and an optional scramjet$token for request/response pairing.

URL metadata

Throughout the rewriting process, Scramjet uses a URLMeta object to track context:
type URLMeta = {
  origin: URL;           // The real origin URL of the page
  base: URL;             // The base URL for resolving relative URLs
  topFrameName?: string; // Name of the topmost Scramjet-controlled frame
  parentFrameName?: string; // Name of the parent frame
};
This metadata is passed to all rewriter functions to ensure URLs are rewritten correctly based on the current page context.
The topFrameName and parentFrameName are critical for handling nested iframes correctly. Always use ScramjetFrame to create iframes instead of raw <iframe> elements.

Component initialization order

  1. ScramjetController is instantiated with configuration
  2. Controller calls init(), which:
    • Opens IndexedDB and stores config
    • Sends config to the Service Worker
    • Sets up message listeners
  3. ScramjetFrame instances are created as needed
  4. When a frame loads, ScramjetClient is injected and initialized
  5. Client hooks DOM and JavaScript APIs
  6. ScramjetServiceWorker begins intercepting requests for that client

Type definitions

Key interfaces defined in src/types.ts:
interface ScramjetConfig {
  prefix: string;
  globals: { /* runtime function names */ };
  files: { wasm: string; all: string; sync: string };
  flags: ScramjetFlags;
  siteFlags: Record<string, Partial<ScramjetFlags>>;
  codec: { encode: string; decode: string };
}

Next steps

Service Worker

Learn how requests are intercepted and proxied

URL rewriting

Understand URL encoding, decoding, and rewriting

Configuration

Explore configuration options and flags

API Reference

Browse the complete API documentation