UNPKG

@redocly/cli

Version:

[@Redocly](https://redocly.com) CLI is your all-in-one OpenAPI utility. It builds, manages, improves, and quality-checks your OpenAPI descriptions, all of which comes in handy for various phases of the API Lifecycle. Create your own rulesets to make API g

188 lines 7.83 kB
/* istanbul ignore file */ // Forked from: // https://github.com/exogen/node-fetch-har // Changes made: // - Allow to pass body as FormData // - removed nanoid and replaced with crypto.randomUUID // - migrated to be used with undici import { URL } from 'url'; import { Client } from 'undici'; import { addHeaders } from './helpers/add-headers.js'; import { getDuration } from './helpers/get-duration.js'; import { buildRequestCookies } from './helpers/build-request-cookies.js'; import { buildHeaders } from './helpers/build-headers.js'; import { buildResponseCookies } from './helpers/build-response-cookies.js'; const HAR_HEADER_NAME = 'x-har-request-id'; const harEntryMap = new Map(); export const withHar = function (baseFetch, defaults = {}) { withHar.harEntryMap = harEntryMap; return async function fetch(input, options = {}) { const { har = defaults.har, harPageRef = defaults.harPageRef, onHarEntry = defaults.onHarEntry, } = options; if (har === false) { return baseFetch(input, options); } const requestId = crypto.randomUUID(); const startTime = process.hrtime(); const url = new URL(typeof input === 'string' ? input : input.url); const entry = { _compressed: false, _resourceType: 'fetch', _timestamps: { start: startTime, socket: startTime, lookup: startTime, connect: startTime, secureConnect: startTime, sent: startTime, firstByte: startTime, received: startTime, }, timings: { blocked: -1, dns: -1, connect: -1, send: 0, wait: 0, receive: 0, ssl: -1, }, time: 0, startedDateTime: new Date().toISOString(), cache: { beforeRequest: null, afterRequest: null, }, request: { method: options.method || 'GET', url: url.href, cookies: buildRequestCookies(options.headers || {}), headers: buildHeaders(options.headers || {}), queryString: [...url.searchParams].map(([name, value]) => ({ name, value, })), headersSize: -1, bodySize: -1, postData: {}, httpVersion: 'HTTP/1.1', }, response: {}, pageref: '', }; // Replace the Dispatcher initialization with Client const client = options.dispatcher || new Client(url.origin); // Listen to Undici dispatcher events client.on('connect', () => (entry._timestamps.connect = process.hrtime())); // Pass the dispatcher in options options = Object.assign({}, options, { headers: addHeaders(options.headers, { [HAR_HEADER_NAME]: requestId }), dispatcher: client, // Use client as dispatcher }); harEntryMap.set(requestId, entry); // Update sent time just before the request entry._timestamps.sent = process.hrtime(); // Make the request const response = await baseFetch(input, options); // Need to clone response to get both text and arrayBuffer const responseClone = response.clone(); // Update firstByte time when we get the response entry._timestamps.firstByte = process.hrtime(); // Get the response body and update received time const text = await response.text(); entry._timestamps.received = process.hrtime(); const harEntry = harEntryMap.get(requestId); harEntryMap.delete(requestId); if (!harEntry) { return response; } // Add response info if (!harEntry.response) { harEntry.response = {}; } // Calculate total bytes of headers including the double CRLF before body const headerLines = [...response.headers.entries()].map(([name, value]) => `${name}: ${value}`); const statusLine = `HTTP/1.1 ${response.status} ${response.statusText}`; const headerBytes = Buffer.byteLength(statusLine + '\r\n' + headerLines.join('\r\n') + '\r\n\r\n'); harEntry._compressed = /^(gzip|compress|deflate|br)$/.test(response.headers.get('content-encoding') || ''); if (!harEntry.response.content) { harEntry.response.content = { size: -1, }; } if (harEntry._compressed) { const rawBody = await responseClone.arrayBuffer(); harEntry.response.content.size = rawBody.byteLength; } else { harEntry.response.content.size = text ? Buffer.byteLength(text) : -1; } const bodySize = text ? Buffer.byteLength(text) : -1; harEntry.response = { headers: buildHeaders(response.headers), cookies: buildResponseCookies(response.headers), status: response.status, statusText: response.statusText || '', httpVersion: response.httpVersion ? `HTTP/${response.httpVersion}` : 'HTTP/1.1', redirectURL: response.headers.location || '', content: { size: harEntry._compressed && harEntry.response.content.size !== -1 ? harEntry.response.content.size : Buffer.byteLength(text), mimeType: response.headers.get('content-type') || '', text, compression: harEntry._compressed && harEntry.response.content.size !== -1 ? harEntry.response.content.size - bodySize : 0, }, bodySize, headersSize: headerBytes, }; // Calculate timings const { _timestamps: time } = harEntry; harEntry.timings = { blocked: Math.max(getDuration(time.start, time.socket), 0.01), dns: -1, connect: Math.max(getDuration(time.lookup, time.connect), -1), ssl: time.secureConnect ? Math.max(getDuration(time.connect, time.secureConnect), -1) : -1, send: getDuration(time.secureConnect || time.connect, time.sent), wait: Math.max(getDuration(time.sent, time.firstByte), 0), receive: getDuration(time.firstByte, time.received), }; // Calculate total time harEntry.time = getDuration(time.start, time.received); const parents = []; let child = harEntry; do { const parent = child._parent; delete child._parent; if (parent) { parents.unshift(parent); } child = parent; } while (child); // Allow grouping by pages. entry.pageref = harPageRef || 'page_1'; parents.forEach((parent) => { parent.pageref = entry.pageref; }); const Response = defaults.Response || baseFetch.Response || global.Response || response.constructor; const responseCopy = new Response(text, { status: response.statusCode, statusText: response.statusText || '', headers: response.headers, url: response.url, }); responseCopy.harEntry = entry; if (har && typeof har === 'object') { har.log.entries.push(...parents, entry); } if (onHarEntry) { parents.forEach((parent) => { onHarEntry(parent); }); onHarEntry(entry); } return responseCopy; }; }; //# sourceMappingURL=with-har.js.map