Skip to main content
Scramjet provides a comprehensive event system that allows you to react to various actions happening within proxified contexts. This guide covers all available events and how to use them.

Event types

Scramjet provides two categories of events:
  1. Frame events - Events specific to a ScramjetFrame instance
  2. Global events - Events at the ScramjetController level

Frame events

Frame events are fired on ScramjetFrame instances and allow you to track navigation within specific iframes. Fired when a frame begins navigating to a new URL:
const frame = scramjet.createFrame();

frame.addEventListener('navigate', (event) => {
  console.log('Navigating to:', event.url);
  // event.url is the real URL being navigated to
});

frame.go('https://example.com');
// Console: "Navigating to: https://example.com"
The navigate event fires before the navigation completes, making it useful for showing loading indicators or logging.

URL change event

Fired when the URL in a frame changes (including history navigation):
frame.addEventListener('urlchange', (event) => {
  console.log('URL changed to:', event.url);
  // Update your UI
  document.getElementById('urlBar').value = event.url;
  document.title = event.url;
});

// Fires on navigation
frame.go('https://example.com');

// Also fires on back/forward
frame.back();
frame.forward();
Use urlchange to keep your UI in sync with the current URL, such as updating address bars or page titles.

Context init event

Fired when Scramjet initializes within a frame:
frame.addEventListener('contextInit', (event) => {
  console.log('Frame initialized!');
  console.log('Window object:', event.window);
  console.log('Client instance:', event.client);

  // You can now safely interact with the frame's context
  const client = event.client;
  console.log('Current URL:', client.url);
});

Event types reference

type ScramjetEvents = {
  navigate: NavigateEvent;
  urlchange: UrlChangeEvent;
  contextInit: ScramjetContextEvent;
};

class NavigateEvent extends Event {
  type = 'navigate';
  url: string; // The URL being navigated to
}

class UrlChangeEvent extends Event {
  type = 'urlchange';
  url: string; // The new URL
}

class ScramjetContextEvent extends Event {
  type = 'contextInit';
  window: Self; // The frame's window object
  client: ScramjetClient; // The ScramjetClient instance
}

Global events

Global events are fired on the ScramjetController instance and affect all frames.

Download events

Intercept file downloads from proxified pages:
1

Enable download interception

Set the interceptDownloads flag when creating the controller:
const scramjet = new ScramjetController({
  prefix: '/scramjet/',
  flags: {
    interceptDownloads: true,
  },
});

await scramjet.init();
2

Listen for download events

Add an event listener to the controller:
scramjet.addEventListener('download', (event) => {
  const download = event.download;
  console.log('Download intercepted:', download);
});
3

Handle the download

Process the download data:
scramjet.addEventListener('download', async (event) => {
  const { filename, url, type, body, length } = event.download;

  console.log('Filename:', filename);
  console.log('URL:', url);
  console.log('Content-Type:', type);
  console.log('Size:', length, 'bytes');

  // Read the download body
  const reader = body.getReader();
  const chunks = [];

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    chunks.push(value);
  }

  // Create a blob from chunks
  const blob = new Blob(chunks, { type });

  // Create a download link
  const link = document.createElement('a');
  link.href = URL.createObjectURL(blob);
  link.download = filename || 'download';
  link.click();

  // Clean up
  URL.revokeObjectURL(link.href);
});

Download event structure

type ScramjetDownload = {
  filename?: string; // Suggested filename from Content-Disposition
  url: string; // The URL being downloaded
  type: string; // MIME type from Content-Type header
  body: ReadableStream<Uint8Array>; // The file contents as a stream
  length: number; // File size in bytes
};

class ScramjetGlobalDownloadEvent extends Event {
  type = 'download';
  download: ScramjetDownload;
}

Practical examples

Building a URL bar

Create a functional URL bar that stays in sync with navigation:
const urlBar = document.getElementById('urlBar');
const goButton = document.getElementById('goButton');

const frame = scramjet.createFrame();
document.body.appendChild(frame.frame);

// Update URL bar on navigation
frame.addEventListener('urlchange', (event) => {
  urlBar.value = event.url;
});

// Navigate when user submits URL
goButton.addEventListener('click', () => {
  let url = urlBar.value;
  if (!url.match(/^https?:\/\//)) {
    url = 'https://' + url;
  }
  frame.go(url);
});

urlBar.addEventListener('keypress', (e) => {
  if (e.key === 'Enter') {
    goButton.click();
  }
});
Track navigation history:
const history = [];
let historyIndex = -1;

frame.addEventListener('navigate', (event) => {
  // Remove any forward history
  history.splice(historyIndex + 1);

  // Add new entry
  history.push(event.url);
  historyIndex = history.length - 1;

  console.log('History:', history);
});

// Custom back/forward with history
function navigateBack() {
  if (historyIndex > 0) {
    historyIndex--;
    frame.go(history[historyIndex]);
  }
}

function navigateForward() {
  if (historyIndex < history.length - 1) {
    historyIndex++;
    frame.go(history[historyIndex]);
  }
}

Loading indicator

Show a loading indicator during navigation:
const loader = document.getElementById('loader');

frame.addEventListener('navigate', (event) => {
  loader.style.display = 'block';
});

frame.addEventListener('urlchange', (event) => {
  // Give the page a moment to render
  setTimeout(() => {
    loader.style.display = 'none';
  }, 500);
});

Download manager

Build a complete download manager:
const downloadList = document.getElementById('downloadList');
const downloads = [];

scramjet.addEventListener('download', async (event) => {
  const { filename, url, type, body, length } = event.download;

  // Create download item
  const item = {
    id: Date.now(),
    filename: filename || 'download',
    url,
    type,
    size: length,
    progress: 0,
    status: 'downloading',
  };

  downloads.push(item);
  renderDownloadList();

  try {
    // Read the body with progress tracking
    const reader = body.getReader();
    const chunks = [];
    let received = 0;

    while (true) {
      const { done, value } = await reader.read();
      if (done) break;

      chunks.push(value);
      received += value.length;

      // Update progress
      item.progress = (received / length) * 100;
      renderDownloadList();
    }

    // Create blob and download
    const blob = new Blob(chunks, { type });
    const link = document.createElement('a');
    link.href = URL.createObjectURL(blob);
    link.download = item.filename;
    link.click();

    item.status = 'completed';
    renderDownloadList();

    // Clean up
    URL.revokeObjectURL(link.href);
  } catch (error) {
    item.status = 'failed';
    item.error = error.message;
    renderDownloadList();
  }
});

function renderDownloadList() {
  downloadList.innerHTML = downloads
    .map(
      (item) => `
      <div class="download-item">
        <div class="filename">${item.filename}</div>
        <div class="progress">
          <div class="bar" style="width: ${item.progress}%"></div>
        </div>
        <div class="status">${item.status}</div>
      </div>
    `
    )
    .join('');
}

Multi-frame event coordination

Coordinate events across multiple frames:
const frames = [];

function createManagedFrame() {
  const frame = scramjet.createFrame();

  // Track this frame
  frames.push(frame);

  // Add event listeners
  frame.addEventListener('navigate', (event) => {
    console.log(`Frame ${frames.indexOf(frame)}: navigating to ${event.url}`);
  });

  frame.addEventListener('urlchange', (event) => {
    console.log(`Frame ${frames.indexOf(frame)}: URL changed to ${event.url}`);

    // Update a global status
    updateGlobalStatus();
  });

  return frame;
}

function updateGlobalStatus() {
  const urls = frames.map((f) => f.url.href);
  console.log('All frame URLs:', urls);
}

// Create multiple frames
const frame1 = createManagedFrame();
const frame2 = createManagedFrame();

frame1.go('https://example.com');
frame2.go('https://example.org');

Event listener options

Scramjet events support standard addEventListener options:
// Remove listener after first trigger
frame.addEventListener(
  'navigate',
  (event) => {
    console.log('First navigation:', event.url);
  },
  { once: true }
);

// Use capture phase
frame.addEventListener(
  'urlchange',
  (event) => {
    console.log('URL changed (capture):', event.url);
  },
  { capture: true }
);

// Passive listener
frame.addEventListener(
  'navigate',
  (event) => {
    console.log('Passive navigate:', event.url);
  },
  { passive: true }
);

Removing event listeners

Remove event listeners when they’re no longer needed:
const handleNavigate = (event) => {
  console.log('Navigate:', event.url);
};

// Add listener
frame.addEventListener('navigate', handleNavigate);

// Remove listener
frame.removeEventListener('navigate', handleNavigate);
Store listener references in variables if you need to remove them later.

Working with frames

Learn about ScramjetFrame and navigation methods

Configuration flags

Enable download interception and other features