Skip to content

Instantly share code, notes, and snippets.

@tluyben
Created March 15, 2025 13:59
Show Gist options
  • Save tluyben/cadbf8fe92782cc712a2a972fdcd4904 to your computer and use it in GitHub Desktop.
Save tluyben/cadbf8fe92782cc712a2a972fdcd4904 to your computer and use it in GitHub Desktop.
function monitorAllNetworkTraffic(callback = null) {
// Store original methods
const originalFetch = window.fetch;
const originalXHROpen = XMLHttpRequest.prototype.open;
const originalXHRSend = XMLHttpRequest.prototype.send;
const originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
// Collection of all messages in order
const messageLog = [];
// Map to store XHR request data by XHR instance
const xhrMap = new WeakMap();
// Function to add messages to the log
function logMessage(message) {
// Add timestamp if not present
if (!message.timestamp) {
message.timestamp = Date.now();
}
// Add to log
messageLog.push(message);
// Print to console
console.log(JSON.stringify(message, null, 2));
// Forward to callback if provided
if (callback) {
callback(message);
}
}
// Cleanup function to remove our interceptors
function stopMonitoring() {
window.fetch = originalFetch;
XMLHttpRequest.prototype.open = originalXHROpen;
XMLHttpRequest.prototype.send = originalXHRSend;
XMLHttpRequest.prototype.setRequestHeader = originalXHRSetRequestHeader;
return messageLog;
}
// Intercept XMLHttpRequest
XMLHttpRequest.prototype.open = function(method, url) {
const requestId = Date.now() + Math.random().toString(36).substr(2, 9);
// Store request info
xhrMap.set(this, {
method,
url,
requestId,
startTime: Date.now(),
headers: {}
});
// Log the request start
logMessage({
type: 'xhr-request-start',
requestId,
url,
method,
timestamp: Date.now()
});
// Add event listener for response
this.addEventListener('load', function() {
const requestInfo = xhrMap.get(this);
if (!requestInfo) return;
let responseData;
try {
// Try to parse as JSON first
responseData = JSON.parse(this.responseText);
} catch (e) {
// If not JSON, use the raw text
responseData = this.responseText;
}
logMessage({
type: 'xhr-response-complete',
requestId: requestInfo.requestId,
status: this.status,
statusText: this.statusText,
headers: this.getAllResponseHeaders().split('\r\n').reduce((acc, line) => {
const parts = line.split(': ');
if (parts[0] && parts[1]) {
acc[parts[0]] = parts[1];
}
return acc;
}, {}),
body: responseData,
duration: Date.now() - requestInfo.startTime,
timestamp: Date.now()
});
});
// Add event listener for errors
this.addEventListener('error', function() {
const requestInfo = xhrMap.get(this);
if (!requestInfo) return;
logMessage({
type: 'xhr-error',
requestId: requestInfo.requestId,
error: 'Network error',
timestamp: Date.now()
});
});
// Call the original open
return originalXHROpen.apply(this, arguments);
};
// Intercept setRequestHeader
XMLHttpRequest.prototype.setRequestHeader = function(header, value) {
const requestInfo = xhrMap.get(this);
if (requestInfo) {
requestInfo.headers[header] = value;
}
return originalXHRSetRequestHeader.apply(this, arguments);
};
// Intercept send
XMLHttpRequest.prototype.send = function(body) {
const requestInfo = xhrMap.get(this);
if (requestInfo && body) {
let requestBody;
try {
requestBody = typeof body === 'string' ? JSON.parse(body) : body;
} catch (e) {
requestBody = body;
}
logMessage({
type: 'xhr-request-body',
requestId: requestInfo.requestId,
headers: requestInfo.headers,
body: requestBody,
timestamp: Date.now()
});
}
return originalXHRSend.apply(this, arguments);
};
// Custom implementation to intercept fetch
window.fetch = async function(resource, options = {}) {
const url = resource instanceof Request ? resource.url : resource;
const fetchMethod = (options.method || (resource instanceof Request ? resource.method : 'GET')).toUpperCase();
// Track the original call timing
const startTime = Date.now();
const requestId = Date.now() + Math.random().toString(36).substr(2, 9);
// Log that we found a request
logMessage({
type: 'fetch-request-start',
requestId,
url,
method: fetchMethod,
timestamp: startTime
});
// Track request body if available
if (options.body) {
try {
const requestBody = typeof options.body === 'string'
? JSON.parse(options.body)
: options.body;
logMessage({
type: 'fetch-request-body',
requestId,
body: requestBody,
headers: options.headers || {},
timestamp: Date.now()
});
} catch (e) {
logMessage({
type: 'fetch-request-body',
requestId,
body: options.body,
headers: options.headers || {},
timestamp: Date.now()
});
}
}
try {
// Call the original fetch
const response = await originalFetch.apply(this, arguments);
// Create a clone to read the body (because response body can only be read once)
const clonedResponse = response.clone();
// Handle streaming or regular response
try {
// For streaming responses
const reader = clonedResponse.body.getReader();
let chunks = [];
// Read the stream
const processStream = async () => {
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
// Stream is complete
break;
}
// Convert the chunk to text
const chunk = new TextDecoder().decode(value);
chunks.push(chunk);
// Try to parse JSON chunks (for streaming APIs)
try {
const jsonData = JSON.parse(chunk);
logMessage({
type: 'response-chunk',
chunk: jsonData,
timestamp: Date.now()
});
} catch (e) {
// Not a valid JSON, just send the raw chunk
logMessage({
type: 'response-chunk',
chunk: chunk,
timestamp: Date.now()
});
}
}
// Concatenate all chunks
const fullBody = chunks.join('');
// Try to parse the complete response
try {
const jsonResponse = JSON.parse(fullBody);
const completeMsg = {
type: 'fetch-response-complete',
requestId,
status: response.status,
headers: Object.fromEntries([...response.headers.entries()]),
body: jsonResponse,
duration: Date.now() - startTime,
timestamp: Date.now()
};
logMessage(completeMsg);
} catch (e) {
// Not JSON, return as text
const completeMsg = {
type: 'fetch-response-complete',
requestId,
status: response.status,
headers: Object.fromEntries([...response.headers.entries()]),
body: fullBody,
duration: Date.now() - startTime,
timestamp: Date.now()
};
logMessage(completeMsg);
}
} catch (streamError) {
logMessage({
type: 'fetch-stream-error',
requestId,
error: streamError.message,
timestamp: Date.now()
});
}
};
// Start processing the stream
processStream();
} catch (streamSetupError) {
// Fallback to regular response handling if streaming fails
handleRegularResponse(clonedResponse, startTime);
}
// Return the original response so the application works normally
return response;
} catch (error) {
logMessage({
type: 'fetch-error',
requestId,
error: error.message,
timestamp: Date.now()
});
throw error; // Re-throw to not interfere with app error handling
}
};
// Helper function to handle regular (non-streaming) responses
async function handleRegularResponse(response, startTime) {
try {
let responseData;
const contentType = response.headers.get('content-type') || '';
if (contentType.includes('application/json')) {
responseData = await response.json();
} else {
responseData = await response.text();
}
const completeMsg = {
type: 'fetch-response-complete',
requestId,
status: response.status,
headers: Object.fromEntries([...response.headers.entries()]),
body: responseData,
duration: Date.now() - startTime,
timestamp: Date.now()
};
logMessage(completeMsg);
} catch (error) {
logMessage({
type: 'fetch-error',
requestId,
error: error.message,
timestamp: Date.now()
});
}
}
// Check if we can intercept Beacon API
if (navigator.sendBeacon) {
const originalSendBeacon = navigator.sendBeacon;
navigator.sendBeacon = function(url, data) {
const beaconId = Date.now() + Math.random().toString(36).substr(2, 9);
// Log the beacon request
logMessage({
type: 'beacon-request',
beaconId,
url,
timestamp: Date.now()
});
// Try to log the data
if (data) {
let beaconData;
try {
if (data instanceof Blob) {
logMessage({
type: 'beacon-data',
beaconId,
dataType: 'Blob',
size: data.size,
timestamp: Date.now()
});
} else if (data instanceof FormData) {
logMessage({
type: 'beacon-data',
beaconId,
dataType: 'FormData',
timestamp: Date.now()
});
} else if (typeof data === 'string') {
try {
beaconData = JSON.parse(data);
logMessage({
type: 'beacon-data',
beaconId,
dataType: 'JSON',
data: beaconData,
timestamp: Date.now()
});
} catch (e) {
logMessage({
type: 'beacon-data',
beaconId,
dataType: 'string',
data: data,
timestamp: Date.now()
});
}
} else {
logMessage({
type: 'beacon-data',
beaconId,
dataType: typeof data,
data: data,
timestamp: Date.now()
});
}
} catch (e) {
logMessage({
type: 'beacon-data-error',
beaconId,
error: e.message,
timestamp: Date.now()
});
}
}
return originalSendBeacon.apply(navigator, arguments);
};
// Update the stop monitoring function
const originalStopMonitoring = stopMonitoring;
stopMonitoring = function() {
navigator.sendBeacon = originalSendBeacon;
return originalStopMonitoring();
};
}
// Log that we've started monitoring
console.log('Monitoring all network traffic (fetch, XHR, and Beacon)...');
// Return the function to stop monitoring
return stopMonitoring;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment