HTTP Archives (HARs) are the de-facto1 standard for exporting network debugger logs from browsers. The acronym HAR is used to refer to both the JSON object data format and the files into which the format is written, which use the extension .har
.2
This module will generate HTTP Archives in HAR format from Page.
and Network.
debugger events generated by the Chrome DevTools Protocol. These events can be recorded by browser extensions and via headless browser tools such as Puppeteer and Playwright3.
hardy-har
is backwards compatible with the chrome-har
API, from which it still borrows a small amount of code, a wealth of test cases, and a huge amount of expertise.4 I created hardy-har
because I wanted/needed:
- support for archiving Web Sockets messages,
- strong typings (a fully-typed all TypeScript codebase), and,
- to understand the code well enough to be confident in its accuracy in a court of law.
Further, I wanted code that was I could easily understand. The code is architected with intention, using a declarative structure that ensures each value produced in a HAR archive can be traced to a single point of calculation. The style is intended not just to make the code easier to use, but to make it easier to read, maintain, verify, and debug: hardy.
NPM's algorithms wouldn't let me register hardy-har
because it was too close to someone else's abandoned package. The joke's on them, because their algorithm allowed the next incremental option (in unary).
npm install @uppajung/hardy-har
// requires types from @types/chrome
import {
type HarEvent,
type HarEventNameAndObject,
type DevToolsProtocolGetResponseBodyRequest,
type DevToolsProtocolGetResponseBodyResponse,
GetResponseBodyResponseMetaEventName,
isHarEventName,
harFromNamedDebuggerEvents,
} from "hardy-har";
export const recordBrowserTabToHarFromWithinExtension = async (
tabId: number,
executeBrowserTaskToRecord: () => Promise<void>
) => {
const debuggerEventArray = [] as HarEventNameAndObject[];
const onDebugEvent = async (source: chrome.debugger.Debuggee, eventName: string, event: unknown) => {
// Ignore debugger events for other tabs
if (source.tabId !== tabId) return;
// Ignore events that aren't needed to generate HARs
if (!isHarEventName(eventName)) return;
debuggerEventArray.push({eventName, event: event as HarEvent<typeof eventName>});
if (eventName === 'Network.loadingFinished') {
// The chrome Network protocol doesn't provide response bodies unless you ask.
const requestId = (event as HarEvent<typeof eventName>).requestId;
// Request the response body
const responseBodyObj = (await (chrome.debugger.sendCommand(
{tabId},
"Network.getResponseBody",
{requestId} satisfies DevToolsProtocolGetResponseBodyRequest)
)) as DevToolsProtocolGetResponseBodyResponse | undefined;
if (responseBodyObj != null) {
// Record a meta event consisting of the requestId and the response body, as if the Chrome DevTools protocol
// had been generous enough to volunteer this information without us begging for it.
debuggerEventArray.push({
eventName: GetResponseBodyResponseMetaEventName,
event: {requestId, ...responseBodyObj} satisfies HarEvent<typeof GetResponseBodyResponseMetaEventName>
});
}
}
}
try {
await chrome.debugger.attach({tabId}, '1.3');
await chrome.debugger.sendCommand({tabId}, "Page.enable");
await chrome.debugger.sendCommand({tabId}, "Network.enable");
chrome.debugger.onEvent.addListener(onDebugEvent);
await executeBrowserTaskToRecord();
return harFromNamedDebuggerEvents(debuggerEventArray);
} finally {
await chrome.debugger.detach({tabId});
}
}
Just replace harFromMessages
with harFromChromeHarMessageParamsObjects
.
If you follow the chrome-har
convention and embed captured response bodies
to a network event, such as Network.loadingFinished
, hardy-har
should
still find and include them.
import {harFromChromeHarMessageParamsObjects} from "jsr:@stuartschechter/hardy-har";
// ... follow chrome-har example to generate events and options
harFromChromeHarMessageParamsObjects(harEvents, options);
For typings of the debugger events generated by the Chrome DevTools Protocol and consumed by this module, import the NPM devtools-protocol
package.
For HAR format typings, use the NPM @types/har-format
package.
- Test cases for web sockets
- Create additional test cases by recording a tab in Chrome via the debugger UI while also capturing the debugger API to generate a hardy-har .har. Then compare the two.
- Add more examples.
Released under the MIT License.
Footnotes
-
The standard is "frozen" though supports extensions made by adding fields starting with underscores (
_
). Such extensions are how the "frozen" standard was extended by the Chrome Team to support Web Sockets. ↩ -
By implication, one might refer to a HTTP Archive file generated by this module as a hardy-har .har. ↩
-
Though Playwright has built-in support for recording HAR files from its internal data structures and the code, while undocumented, looks fairly modern and well architected. ↩
-
Whether
hardy-har
is a port or re-write ofchrome-har
is a question made largely irrelevant by thechrome-har
team's generous use of the MIT License. Regardless, they are owed much gratitude. ↩