> ## 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.

# Event handling

> Listen and respond to navigation, URL changes, and download events in Scramjet

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.

### Navigate event

Fired when a frame begins navigating to a new URL:

```javascript theme={null}
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"
```

<Note>
  The `navigate` event fires before the navigation completes, making it useful for showing loading indicators or logging.
</Note>

### URL change event

Fired when the URL in a frame changes (including history navigation):

```javascript theme={null}
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();
```

<Tip>
  Use `urlchange` to keep your UI in sync with the current URL, such as updating address bars or page titles.
</Tip>

### Context init event

Fired when Scramjet initializes within a frame:

```javascript theme={null}
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

```typescript theme={null}
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:

<Steps>
  <Step title="Enable download interception">
    Set the `interceptDownloads` flag when creating the controller:

    ```javascript theme={null}
    const scramjet = new ScramjetController({
      prefix: '/scramjet/',
      flags: {
        interceptDownloads: true,
      },
    });

    await scramjet.init();
    ```
  </Step>

  <Step title="Listen for download events">
    Add an event listener to the controller:

    ```javascript theme={null}
    scramjet.addEventListener('download', (event) => {
      const download = event.download;
      console.log('Download intercepted:', download);
    });
    ```
  </Step>

  <Step title="Handle the download">
    Process the download data:

    ```javascript theme={null}
    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);
    });
    ```
  </Step>
</Steps>

### Download event structure

```typescript theme={null}
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:

```javascript theme={null}
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();
  }
});
```

### Navigation history

Track navigation history:

```javascript theme={null}
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:

```javascript theme={null}
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:

```javascript theme={null}
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:

```javascript theme={null}
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:

```javascript theme={null}
// 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:

```javascript theme={null}
const handleNavigate = (event) => {
  console.log('Navigate:', event.url);
};

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

// Remove listener
frame.removeEventListener('navigate', handleNavigate);
```

<Tip>
  Store listener references in variables if you need to remove them later.
</Tip>

## Related guides

<CardGroup cols={2}>
  <Card title="Working with frames" icon="window" href="/guides/frames">
    Learn about ScramjetFrame and navigation methods
  </Card>

  <Card title="Configuration flags" icon="flag" href="/guides/configuration-flags">
    Enable download interception and other features
  </Card>
</CardGroup>
