> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/mercuryworkshop/scramjet/llms.txt
> Use this file to discover all available pages before exploring further.

# Debugging and troubleshooting

> Techniques for debugging Scramjet, analyzing errors, and troubleshooting common issues

Debugging a web proxy like Scramjet requires understanding both the proxy internals and how browsers execute proxied code. This guide covers debugging techniques, logging utilities, and common pitfalls.

## Logging system

Scramjet includes a custom logging utility in `src/log.ts` that provides formatted console output with stack traces.

### Basic usage

```typescript theme={null}
import dbg from "@/log";

// Standard log levels
dbg.log("Initialization complete");
dbg.warn("Deprecated API used");
dbg.error("Failed to fetch resource", error);
dbg.debug("Variable state:", { foo: "bar" });
```

### Formatted output

The logger automatically formats messages with:

* **Function name** from call stack
* **Severity-based styling** (colors, padding)
* **Caller context** for debugging

```typescript theme={null}
// Output example:
// [rewriteJs] Rewriting script: https://example.com/app.js
//   ↑ function name  ↑ message
```

### Performance timing

Track execution time for operations:

```typescript theme={null}
import dbg from "@/log";

const before = performance.now();
const result = rewriteJs(code, url, meta, false);
dbg.time(meta, before, "JavaScript rewrite");

// Output: [time] JavaScript rewrite was decent speed (23.45ms)
```

The `time()` method categorizes performance:

* **\< 1ms**: "BLAZINGLY FAST"
* **\< 500ms**: "decent speed"
* **≥ 500ms**: "really slow"

<Note>
  Performance timing is only active when the `rewriterLogs` flag is enabled.
</Note>

## Feature flags

Scramjet uses feature flags to control debugging and experimental features.

### Available flags

```typescript theme={null}
interface ScramjetFlags {
  // Debugging
  rewriterLogs: boolean;        // Enable rewriter debug logs
  cleanErrors: boolean;         // Clean stack traces
  captureErrors: boolean;       // Capture and log errors
  sourcemaps: boolean;          // Generate sourcemaps
  
  // Features
  serviceworkers: boolean;      // Enable SW registration
  syncxhr: boolean;             // Synchronous XHR support
  interceptDownloads: boolean;  // Intercept file downloads
  
  // Error handling
  allowInvalidJs: boolean;      // Allow malformed JS
  allowFailedIntercepts: boolean; // Continue on hook failures
}
```

### Enabling flags

```javascript theme={null}
const { ScramjetController } = $scramjetLoadController();

const controller = new ScramjetController({
  prefix: "/service/",
  flags: {
    rewriterLogs: true,     // Enable for debugging
    sourcemaps: true,       // Enable sourcemaps
    cleanErrors: true,      // Clean stack traces
  },
});
```

### Site-specific flags

Override flags for specific domains:

```typescript theme={null}
const controller = new ScramjetController({
  flags: {
    rewriterLogs: false,  // Default off
  },
  siteFlags: {
    "example\\.com": {
      rewriterLogs: true, // Enable for example.com
    },
  },
});
```

### Checking flags at runtime

```typescript theme={null}
import { flagEnabled } from "@/shared";

const url = new URL("https://example.com");

if (flagEnabled("rewriterLogs", url)) {
  console.log("Debug logging enabled for this site");
}

if (flagEnabled("allowInvalidJs", url)) {
  console.warn("Invalid JavaScript will not cause errors");
}
```

## Error handling

### Client-side error capture

Scramjet can capture and analyze errors from proxied sites:

```typescript theme={null}
// In client hooks (src/client/shared/err.ts)
if (flagEnabled("captureErrors", client.url)) {
  self.addEventListener("error", (event) => {
    console.error("Captured error:", {
      message: event.message,
      filename: event.filename,
      lineno: event.lineno,
      colno: event.colno,
      error: event.error,
    });
  });
  
  self.addEventListener("unhandledrejection", (event) => {
    console.error("Unhandled rejection:", event.reason);
  });
}
```

### Service worker error handling

The service worker catches and logs fetch errors:

```typescript theme={null}
// From src/worker/fetch.ts
try {
  return await handleFetch.call(this, request, client);
} catch (err) {
  const errorDetails = {
    message: err.message,
    url: request.url,
    destination: request.destination,
  };
  
  if (err.stack) {
    errorDetails.stack = err.stack;
  }
  
  console.error("ERROR FROM SERVICE WORKER FETCH:", errorDetails);
  
  // Return error page for documents
  if (["document", "iframe"].includes(request.destination)) {
    return renderError(
      Object.entries(errorDetails)
        .map(([key, value]) => `${key}: ${value}`)
        .join("\n\n"),
      unrewriteUrl(request.url)
    );
  }
  
  return new Response(undefined, { status: 500 });
}
```

<Accordion title="Error page rendering">
  Scramjet generates user-friendly error pages:

  ```typescript theme={null}
  // Simplified from src/worker/error.ts
  export function renderError(error: string, url: string) {
    const html = `
      <!DOCTYPE html>
      <html>
        <head>
          <title>Scramjet Error</title>
          <style>
            body { font-family: monospace; padding: 20px; }
            .error { background: #fee; padding: 10px; border: 1px solid #fcc; }
          </style>
        </head>
        <body>
          <h1>Scramjet encountered an error</h1>
          <div class="error">
            <pre>${error}</pre>
          </div>
          <p>URL: <code>${url}</code></p>
        </body>
      </html>
    `;
    
    return new Response(html, {
      status: 500,
      headers: { "Content-Type": "text/html" },
    });
  }
  ```
</Accordion>

### Stack trace cleaning

Scramjet can clean stack traces to hide proxy internals:

```typescript theme={null}
import { flagEnabled } from "@/shared";

if (flagEnabled("cleanErrors", client.url)) {
  const originalPrepare = Error.prepareStackTrace;
  
  Error.prepareStackTrace = (err, stack) => {
    // Filter out Scramjet internal frames
    const filtered = stack.filter(frame => {
      const filename = frame.getFileName();
      return filename && !filename.includes("scramjet");
    });
    
    return filtered
      .map(frame => `  at ${frame.toString()}`)
      .join("\n");
  };
}
```

## Sourcemap debugging

Sourcemaps allow debugging rewritten code as if it were the original.

### Enabling sourcemaps

```typescript theme={null}
const controller = new ScramjetController({
  flags: {
    sourcemaps: true,
  },
});
```

### How sourcemaps work

When enabled, Scramjet injects sourcemap data into rewritten scripts:

```javascript theme={null}
// Original code:
fetch("/api");

// Rewritten with sourcemap:
__scramjet$pushsourcemap([/* binary sourcemap data */], "tag-12345");
scramjet$fetch("/api");
```

The client maintains a sourcemap registry:

```typescript theme={null}
// From src/client/shared/sourcemaps.ts
const sourcemaps = new Map<string, SourceMap>();

globalThis[config.globals.pushsourcemapfn] = (map: number[], tag: string) => {
  const bytes = new Uint8Array(map);
  const decoded = new TextDecoder().decode(bytes);
  const parsed = JSON.parse(decoded);
  
  sourcemaps.set(tag, parsed);
};
```

### Using sourcemaps in DevTools

With sourcemaps enabled:

1. Open Chrome DevTools
2. Enable "Enable JavaScript source maps" in Settings
3. Set breakpoints in the original code
4. Stack traces show original line numbers

<Warning>
  Sourcemaps add overhead to rewriting. Only enable them during development.
</Warning>

## Browser DevTools integration

### Inspecting service worker

1. Open `chrome://serviceworker-internals/`
2. Find your Scramjet service worker
3. Click "Inspect" to open dedicated DevTools
4. View console logs, network requests, and sources

### Network inspection

Monitor proxied requests:

1. Open DevTools → Network tab
2. Filter by "Fetch/XHR" or "WS" for WebSockets
3. Inspect request/response headers
4. Check "Preserve log" to track redirects

<Info>
  Scramjet requests appear as same-origin to DevTools since they're routed through the service worker.
</Info>

### Console filtering

Filter Scramjet logs:

```javascript theme={null}
// Show only Scramjet logs
console.log = new Proxy(console.log, {
  apply(target, thisArg, args) {
    if (args[0]?.includes?.("scramjet")) {
      target.apply(thisArg, args);
    }
  },
});
```

## Common debugging patterns

### Tracking URL rewriting

```typescript theme={null}
import { rewriteUrl, unrewriteUrl } from "@rewriters/url";

const original = "https://example.com/page";
const rewritten = rewriteUrl(original, meta);
const restored = unrewriteUrl(rewritten);

console.log("Original:", original);
console.log("Rewritten:", rewritten);
console.log("Restored:", restored);
console.log("Match:", original === restored);
```

### Debugging client hooks

```typescript theme={null}
client.Proxy("fetch", {
  apply(ctx) {
    console.log("fetch() intercepted:", {
      url: ctx.args[0],
      init: ctx.args[1],
    });
    
    // Continue with normal behavior
  },
});
```

### Monitoring rewriter pool

```typescript theme={null}
import { getRewriter } from "@rewriters/wasm";

const [rewriter, release] = getRewriter(meta);

console.log("Rewriter pool size:", rewriters.length);
console.log("In-use rewriters:", rewriters.filter(r => r.inUse).length);

try {
  const result = rewriter.rewrite_js(code, base, url, module);
} finally {
  release();
}
```

### Analyzing performance

```typescript theme={null}
const metrics = {
  rewriteTime: 0,
  fetchTime: 0,
  totalTime: 0,
};

const start = performance.now();

// Fetch
const fetchStart = performance.now();
const response = await fetch(url);
metrics.fetchTime = performance.now() - fetchStart;

// Rewrite
const rewriteStart = performance.now();
const body = await rewriteBody(response, meta, destination);
metrics.rewriteTime = performance.now() - rewriteStart;

metrics.totalTime = performance.now() - start;

console.table(metrics);
```

## Common pitfalls

### Forgetting to release rewriters

```typescript theme={null}
// BAD: Rewriter never released
const [rewriter, release] = getRewriter(meta);
const result = rewriter.rewrite_js(code, base, url, module);
// Missing: release();

// GOOD: Use try/finally
const [rewriter, release] = getRewriter(meta);
try {
  const result = rewriter.rewrite_js(code, base, url, module);
  return result;
} finally {
  release();
}
```

### Incorrect URL metadata

```typescript theme={null}
// BAD: Missing base URL
const meta: URLMeta = {
  origin: new URL("https://example.com/page"),
  base: undefined, // Error!
};

// GOOD: Always provide base
const meta: URLMeta = {
  origin: new URL("https://example.com/page"),
  base: new URL("https://example.com/page"),
};
```

### Rewriting before WASM loads

```typescript theme={null}
// BAD: WASM not loaded
const result = rewriteJs(code, url, meta, false);
// Error: rewriter wasm not found

// GOOD: Wait for WASM
await scramjet.loadConfig();
// -> calls asyncSetWasm() internally
const result = rewriteJs(code, url, meta, false);
```

### Not handling edge cases

```typescript theme={null}
// BAD: Assumes string input
const rewritten = rewriteJs(code, url, meta, false);
return rewritten.toUpperCase(); // Error if Uint8Array!

// GOOD: Handle both types
const rewritten = rewriteJs(code, url, meta, false);
const str = typeof rewritten === "string"
  ? rewritten
  : new TextDecoder().decode(rewritten);
return str.toUpperCase();
```

### Circular rewriting

```typescript theme={null}
// BAD: Rewriting already-rewritten URLs
const url1 = rewriteUrl(originalUrl, meta);
const url2 = rewriteUrl(url1, meta); // Double-encoded!

// GOOD: Check if already rewritten
const url = originalUrl.startsWith(config.prefix)
  ? originalUrl
  : rewriteUrl(originalUrl, meta);
```

## Debugging checklist

When encountering issues:

<Steps>
  <Step title="Check service worker">
    Verify the service worker is active:

    ```javascript theme={null}
    navigator.serviceWorker.ready.then(reg => {
      console.log("SW active:", reg.active?.state);
    });
    ```
  </Step>

  <Step title="Enable debug flags">
    Turn on logging:

    ```typescript theme={null}
    flags: {
      rewriterLogs: true,
      captureErrors: true,
    }
    ```
  </Step>

  <Step title="Inspect network">
    Open DevTools → Network and filter by type (JS, CSS, Fetch/XHR).
  </Step>

  <Step title="Check console">
    Look for Scramjet errors, warnings, or debug messages.
  </Step>

  <Step title="Verify transport">
    Ensure bare-mux transport is configured:

    ```typescript theme={null}
    await BareClient.SetTransport("/bare/", "bare");
    ```
  </Step>

  <Step title="Test with simple page">
    Try proxying a basic HTML page to isolate the issue.
  </Step>
</Steps>

## Getting help

If you're still stuck:

1. **Check GitHub issues**: Search for similar problems
2. **Enable all debug flags**: Gather comprehensive logs
3. **Create minimal reproduction**: Isolate the issue to a small example
4. **Share logs**: Include console output, network traces, and error messages

<Info>
  When reporting issues, always include your Scramjet version, browser version, and transport configuration.
</Info>
