UNPKG

@pierrad/web-carbon-analyzer

Version:

A tool to measure the carbon footprint of websites using CO2.js

219 lines 8.04 kB
"use strict"; 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