Skip to main content

Quickstart

This guide will walk you through setting up a basic Scramjet proxy. You’ll learn how to register a service worker, initialize the controller, and create a proxied iframe.

Setup overview

A Scramjet proxy requires two main components:
  1. Service worker - Intercepts and rewrites network requests
  2. Controller - Manages frames and communicates with the service worker
1

Create the service worker

Create a file called sw.js in your static directory:
sw.js
importScripts("/scramjet/scramjet.all.js");

const { ScramjetServiceWorker } = $scramjetLoadWorker();
const scramjet = new ScramjetServiceWorker();

async function handleRequest(event) {
  await scramjet.loadConfig();
  
  if (scramjet.route(event)) {
    return scramjet.fetch(event);
  }
  
  return fetch(event.request);
}

self.addEventListener("fetch", (event) => {
  event.respondWith(handleRequest(event));
});
This service worker intercepts all fetch requests. If a request matches Scramjet’s routing (based on the configured prefix), it’s proxied. Otherwise, it’s passed through normally.
2

Set up your HTML page

Create an HTML page that will load Scramjet and register the service worker:
index.html
<!DOCTYPE html>
<html>
  <head>
    <title>Scramjet Proxy</title>
  </head>
  <body>
    <div id="container"></div>
    <script src="/scramjet/scramjet.bundle.js"></script>
    <script src="/app.js"></script>
  </body>
</html>
3

Initialize Scramjet controller

Create app.js to register the service worker and set up the controller:
app.js
// Load the Scramjet controller
const { ScramjetController } = $scramjetLoadController();

// Create a new controller instance
const scramjet = new ScramjetController({
  prefix: "/scramjet/",
  codec: {
    encode: (url) => encodeURIComponent(url),
    decode: (url) => decodeURIComponent(url)
  },
  flags: {
    captureErrors: true,
    strictRewrites: true
  }
});

async function init() {
  // Register the service worker
  if ("serviceWorker" in navigator) {
    await navigator.serviceWorker.register("/sw.js", {
      scope: "/"
    });
    
    // Wait for the service worker to be ready
    await navigator.serviceWorker.ready;
  }
  
  // Initialize Scramjet
  await scramjet.init();
  
  // Create a proxied iframe
  const frame = scramjet.createFrame();
  document.getElementById("container").appendChild(frame.frame);
  
  // Navigate to a URL
  frame.go("https://example.com");
}

init();
The prefix option determines which URL paths are handled by Scramjet. In this example, any request to /scramjet/* will be proxied.
4

Serve the static files

Make sure the Scramjet static files are available at the configured paths:
  • /scramjet/scramjet.bundle.js
  • /scramjet/scramjet.all.js
  • /scramjet/scramjet.wasm.wasm
These files are in the dist directory of the @mercuryworkshop/scramjet package.
5

Test your setup

Start your web server and open the page in a browser. You should see example.com loaded in the iframe through the Scramjet proxy.

Configuration options

The ScramjetController accepts several configuration options:

Basic options

const scramjet = new ScramjetController({
  prefix: "/scramjet/",  // URL prefix for proxied requests
  codec: {
    encode: (url) => encodeURIComponent(url),
    decode: (url) => decodeURIComponent(url)
  },
  files: {
    wasm: "/scramjet.wasm.wasm",
    all: "/scramjet.all.js",
    sync: "/scramjet.sync.js"
  }
});

Feature flags

Control Scramjet’s behavior with flags:
const scramjet = new ScramjetController({
  prefix: "/scramjet/",
  flags: {
    strictRewrites: true,      // Enforce strict URL rewriting
    captureErrors: true,        // Capture and clean errors
    sourcemaps: true,          // Enable source map support
    allowInvalidJs: true       // Allow pages with invalid JS
  }
});
Some flags like syncxhr and serviceworkers enable experimental features that may impact performance or compatibility.

Working with frames

Scramjet provides a ScramjetFrame class for managing proxied iframes:
// Create a frame (Scramjet creates the iframe element)
const frame = scramjet.createFrame();
document.body.appendChild(frame.frame);

// Or use an existing iframe element
const existingIframe = document.getElementById("myframe");
const frame = scramjet.createFrame(existingIframe);

// Navigate to a URL
frame.navigate("https://example.com");

URL encoding and decoding

You can encode and decode URLs using the controller:
// Encode a URL for use in the proxy
const proxiedUrl = scramjet.encodeUrl("https://example.com");
// Returns: "/scramjet/https%3A%2F%2Fexample.com"

// Decode a proxied URL back to the original
const originalUrl = scramjet.decodeUrl(proxiedUrl);
// Returns: "https://example.com"

Using Workbox (optional)

For more advanced service worker routing, you can use Workbox:
sw.js
importScripts("/scramjet/scramjet.all.js");
import { registerRoute } from "workbox-routing";

const { ScramjetServiceWorker } = $scramjetLoadWorker();
const scramjet = new ScramjetServiceWorker();

registerRoute(
  ({ request }) => {
    return scramjet.route({ request });
  },
  async ({ event }) => {
    await scramjet.loadConfig();
    return scramjet.fetch(event);
  }
);
Workbox routing is useful if you’re building a PWA with offline support alongside your proxy functionality.

Next steps

Now that you have a working Scramjet setup, you can:
  • Customize the codec to implement custom URL encoding schemes
  • Add event listeners to handle navigation and downloads
  • Configure site-specific flags for better compatibility
  • Explore the TypeScript API documentation for advanced features

Common issues

If CAPTCHAs aren’t working, make sure you’re not hosting on a datacenter IP. Residential IPs work best for CAPTCHA-heavy sites like Google and YouTube.
Service workers require HTTPS in production environments (except for localhost). Make sure your site is served over HTTPS.

Build docs developers (and LLMs) love