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:
Frame events - Events specific to a ScramjetFrame instance
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:
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:
Enable download interception
Set the interceptDownloads flag when creating the controller: const scramjet = new ScramjetController ({
prefix: '/scramjet/' ,
flags: {
interceptDownloads: true ,
},
});
await scramjet . init ();
Listen for download events
Add an event listener to the controller: scramjet . addEventListener ( 'download' , ( event ) => {
const download = event . download ;
console . log ( 'Download intercepted:' , download );
});
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 ();
}
});
Navigation history
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