@pierrad/web-carbon-analyzer
Version:
A tool to measure the carbon footprint of websites using CO2.js
219 lines • 8.04 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const logger_1 = __importDefault(require("../utils/logger"));
class NetworkInterceptor {
constructor() {
this.resources = [];
this.startTime = null;
this.endTime = null;
}
/**
* Setup network interception on a Playwright page
* @param {Page} page - Playwright page object
*/
async setupInterception(page) {
if (!page) {
throw new Error('Page object is required');
}
logger_1.default.info('Setting up network interception');
this.startTime = Date.now();
this.resources = [];
// Listen for request events
page.on('request', request => {
this.onRequest(request);
});
// Listen for response events
page.on('response', response => {
this.onResponse(response);
});
// Listen for request finished events
page.on('requestfinished', request => {
this.onRequestFinished(request);
});
// Listen for request failed events
page.on('requestfailed', request => {
this.onRequestFailed(request);
});
logger_1.default.info('Network interception set up successfully');
}
/**
* Handle request events
* @param {Request} request - Playwright request object
*/
onRequest(request) {
try {
const url = request.url();
const resourceType = request.resourceType();
const method = request.method();
logger_1.default.debug(`Request started: ${method} ${url} (${resourceType})`);
// Store initial request data
const resourceData = {
url,
resourceType,
method,
requestTime: Date.now(),
responseTime: null,
size: {
compressed: null,
uncompressed: null
},
status: null,
headers: {
request: request.headers(),
response: null
},
fromCache: false,
error: null
};
// Use request hash as identifier
const requestId = request.url();
this.resources.push({
id: requestId,
...resourceData
});
}
catch (error) {
logger_1.default.error(`Error processing request: ${error.message}`);
}
}
/**
* Handle response events
* @param {Response} response - Playwright response object
*/
async onResponse(response) {
try {
const request = response.request();
const url = request.url();
const status = response.status();
logger_1.default.debug(`Response received: ${status} ${url}`);
// Find the resource in our collection
const resource = this.resources.find(r => r.id === url);
if (resource) {
// Update with response data
resource.status = status;
resource.responseTime = Date.now();
resource.headers.response = response.headers();
// Try to get content size from Content-Length header
const contentLength = response.headers()['content-length'];
if (contentLength) {
resource.size.compressed = parseInt(contentLength, 10);
}
// Check if it's from cache based on headers
resource.fromCache = this.isResponseCached(response);
}
}
catch (error) {
logger_1.default.error(`Error processing response: ${error.message}`);
}
}
/**
* Handle request finished events
* @param {Request} request - Playwright request object
*/
onRequestFinished(request) {
this.endTime = Date.now();
return Promise.resolve();
}
/**
* Handle request failed events
* @param {Request} request - Playwright request object
*/
onRequestFailed(request) {
try {
const url = request.url();
const failureText = request.failure()?.errorText || 'Unknown error';
logger_1.default.debug(`Request failed: ${url} - ${failureText}`);
// Find the resource in our collection
const resource = this.resources.find(r => r.id === url);
if (resource) {
// Update with error information
resource.error = failureText;
}
}
catch (error) {
logger_1.default.error(`Error processing request failure: ${error.message}`);
}
}
/**
* Determine if a response was served from browser cache
* @param {Response} response - Playwright response object
* @returns {boolean} - Whether the response was cached
*/
isResponseCached(response) {
const headers = response.headers();
// Various cache-related headers that indicate a cached response
if (headers['cf-cache-status'] === 'HIT')
return true;
if (headers['x-cache'] === 'HIT')
return true;
if (headers['x-cache-hits'] && headers['x-cache-hits'] !== '0')
return true;
if (headers['x-drupal-cache'] === 'HIT')
return true;
if (headers['x-magento-cache'] === 'HIT')
return true;
return false;
}
/**
* Process the collected resources data into a structured format
* @returns {ResourcesData} - Processed resources data
*/
processResourcesData() {
logger_1.default.info('Processing collected resources data');
if (!this.startTime) {
throw new Error('Network interception not properly initialized');
}
const endTime = this.endTime || Date.now();
const totalDuration = endTime - this.startTime;
// Initialize data structure
const result = {
allResources: this.resources,
totalResources: this.resources.length,
totalSize: 0,
totalDuration,
sizeByType: {},
resourcesByType: {},
domains: {}
};
// Group resources by type and domain
for (const resource of this.resources) {
const resourceType = resource.resourceType;
const size = resource.size.compressed || 0;
// Group by resource type
if (!result.resourcesByType[resourceType]) {
result.resourcesByType[resourceType] = [];
result.sizeByType[resourceType] = 0;
}
result.resourcesByType[resourceType].push(resource);
result.sizeByType[resourceType] += size;
// Extract domain from URL
let domain = '';
try {
domain = new URL(resource.url).hostname;
}
catch (error) {
logger_1.default.debug(`Could not parse URL ${resource.url}: ${error.message}`);
}
// Group by domain
if (domain) {
if (!result.domains[domain]) {
result.domains[domain] = {
size: 0,
count: 0
};
}
result.domains[domain].size += size;
result.domains[domain].count += 1;
}
// Add to total size
result.totalSize += size;
}
logger_1.default.info(`Processed ${result.totalResources} resources, ${result.totalSize} bytes total`);
return result;
}
}
exports.default = NetworkInterceptor;
//# sourceMappingURL=network-interceptor.js.map