Skip to content

Commit 5630516

Browse files
author
Michael Mrowetz
committed
#156 allow headers in overlay to occure multiple times.
1 parent 6ac8cac commit 5630516

5 files changed

Lines changed: 82 additions & 26 deletions

File tree

src/ts/helpers/har.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,37 @@
1-
import {Header} from "har-format";
1+
import { Header } from "har-format";
2+
import { KvTuple } from "../typing/waterfall";
23

3-
function matchHeaderFilter(lowercaseName: string): (header: Header) => boolean {
4+
/** Partial function that buils a filter predicate function */
5+
const matchHeaderPartialFn = (lowercaseName: string): (header: Header) => boolean => {
46
return (header: Header) => header.name.toLowerCase() === lowercaseName;
5-
}
7+
};
68

9+
/**
10+
* @param headers List of `Header` to search in
11+
* @param headerName Name of `Header` to find
12+
*/
713
export function hasHeader(headers: Header[], headerName: string): boolean {
8-
const headerFilter = matchHeaderFilter(headerName.toLowerCase());
14+
const headerFilter = matchHeaderPartialFn(headerName.toLowerCase());
915
return headers.some(headerFilter);
1016
}
1117

18+
/**
19+
* Returns the fist instances of `headerName` in `headers`
20+
* @param headers List of `Header` to search in
21+
* @param headerName Name of `Header` to find
22+
*/
1223
export function getHeader(headers: Header[], headerName: string): string {
13-
const headerFilter = matchHeaderFilter(headerName.toLowerCase());
14-
const firstMatch = headers.filter(headerFilter).pop();
15-
return firstMatch ? firstMatch.value : undefined;
24+
const headerFilter = matchHeaderPartialFn(headerName.toLowerCase());
25+
const firstItem = headers.find(headerFilter);
26+
return firstItem ? firstItem.value : undefined;
27+
}
28+
29+
/**
30+
* Returns all instances of `headerName` in `headers` as `KvTuple`
31+
* @param headers List of `Header` to search in
32+
* @param headerName Name of `Header` to find
33+
*/
34+
export function getHeaders(headers: Header[], headerName: string): KvTuple[] {
35+
const headerFilter = matchHeaderPartialFn(headerName.toLowerCase());
36+
return headers.filter(headerFilter).map((h) => [headerName, h.value] as KvTuple);
1637
}

src/ts/transformers/extract-details-keys.ts

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
import { Entry, Header } from "har-format";
2-
import { getHeader } from "../helpers/har";
2+
import { getHeader, getHeaders } from "../helpers/har";
33
import {
4-
formatBytes, formatDateLocalized, formatMilliseconds, formatSeconds, parseAndFormat, parseDate, parseNonEmpty,
5-
parseNonNegative, parsePositive,
4+
formatBytes,
5+
formatDateLocalized,
6+
formatMilliseconds,
7+
formatSeconds,
8+
parseAndFormat,
9+
parseDate,
10+
parseNonEmpty,
11+
parseNonNegative,
12+
parsePositive,
613
} from "../helpers/parse";
14+
import { KvTuple } from "../typing/waterfall";
15+
import { flattenKvTuple } from "./helpers";
716

817
const byteSizeProperty = (title: string, input: string |  number): KvTuple => {
918
return [title, parseAndFormat(input, parsePositive, formatBytes)];
@@ -12,8 +21,13 @@ const countProperty = (title: string, input: string |  number): KvTuple => {
1221
return [title, parseAndFormat(input, parsePositive)];
1322
};
1423

24+
/** Predicate to filter out invalid or empty `KvTuple` */
25+
const notEmpty = (kv: KvTuple) => {
26+
return kv.length > 1 && kv[1] !== undefined && kv[1] !== "";
27+
};
28+
1529
function parseGeneralDetails(entry: Entry, startRelative: number, requestID: number): KvTuple[] {
16-
return [
30+
return ([
1731
["Request Number", `#${requestID}`],
1832
["Started", new Date(entry.startedDateTime).toLocaleString() + ((startRelative > 0) ?
1933
" (" + formatMilliseconds(startRelative) + " after page request started)" : "")],
@@ -41,15 +55,14 @@ function parseGeneralDetails(entry: Entry, startRelative: number, requestID: num
4155
byteSizeProperty("Minify Save", entry._minify_save),
4256
byteSizeProperty("Image Total", entry._image_total),
4357
byteSizeProperty("Image Save", entry._image_save),
44-
].filter((k) => k[1] !== undefined && k[1] !== "") as KvTuple[];
58+
] as KvTuple[]).filter(notEmpty);
4559
}
4660

4761
function parseRequestDetails(harEntry: Entry): KvTuple[] {
4862
const request = harEntry.request;
63+
const stringHeader = (name: string): KvTuple[] => getHeaders(request.headers, name);
4964

50-
const stringHeader = (name: string): KvTuple => [name, getHeader(request.headers, name)];
51-
52-
return [
65+
return flattenKvTuple([
5366
["Method", request.method],
5467
["HTTP Version", request.httpVersion],
5568
byteSizeProperty("Bytes Out (uploaded)", harEntry._bytesOut),
@@ -68,15 +81,17 @@ function parseRequestDetails(harEntry: Entry): KvTuple[] {
6881
stringHeader("If-Unmodified-Since"),
6982
countProperty("Querystring parameters count", request.queryString.length),
7083
countProperty("Cookies count", request.cookies.length),
71-
].filter((k) => k[1] !== undefined && k[1] !== "") as KvTuple[];
84+
]).filter(notEmpty);
7285
}
7386

7487
function parseResponseDetails(entry: Entry): KvTuple[] {
7588
const response = entry.response;
7689
const content = response.content;
7790
const headers = response.headers;
7891

79-
const stringHeader = (title: string, name: string = title): KvTuple => [title, getHeader(headers, name)];
92+
const stringHeader = (title: string, name: string = title): KvTuple[] => {
93+
return getHeaders(headers, name);
94+
};
8095
const dateHeader = (name: string): KvTuple => {
8196
const header = getHeader(headers, name);
8297
return [name, parseAndFormat(header, parseDate, formatDateLocalized)];
@@ -93,7 +108,7 @@ function parseResponseDetails(entry: Entry): KvTuple[] {
93108
contentType = contentType + " | " + entry._contentType;
94109
}
95110

96-
return [
111+
return flattenKvTuple([
97112
["Status", response.status + " " + response.statusText],
98113
["HTTP Version", response.httpVersion],
99114
byteSizeProperty("Bytes In (downloaded)", entry._bytesIn),
@@ -123,12 +138,11 @@ function parseResponseDetails(entry: Entry): KvTuple[] {
123138
stringHeader("Timing-Allow-Origin"),
124139
["Redirect URL", parseAndFormat(response.redirectURL, parseNonEmpty)],
125140
["Comment", parseAndFormat(response.comment, parseNonEmpty)],
126-
];
141+
]).filter(notEmpty);
127142
}
128143

129144
function parseTimings(entry: Entry, start: number, end: number): KvTuple[] {
130145
const timings = entry.timings;
131-
132146
const optionalTiming = (timing?: number) => parseAndFormat(timing, parseNonNegative, formatMilliseconds);
133147
const total = (typeof start !== "number" || typeof end !== "number") ? undefined : (end - start);
134148

@@ -144,8 +158,6 @@ function parseTimings(entry: Entry, start: number, end: number): KvTuple[] {
144158
];
145159
}
146160

147-
/** Key/Value pair in array `["key", "value"]` */
148-
export type KvTuple = [string, string];
149161

150162
/**
151163
* Data to show in overlay tabs
@@ -155,8 +167,7 @@ export type KvTuple = [string, string];
155167
export function getKeys(entry: Entry, requestID: number, startRelative: number, endRelative: number) {
156168
const requestHeaders = entry.request.headers;
157169
const responseHeaders = entry.response.headers;
158-
159-
let headerToKvTuple = (header: Header): KvTuple => [header.name, header.value];
170+
const headerToKvTuple = (header: Header): KvTuple => [header.name, header.value];
160171

161172
return {
162173
"general": parseGeneralDetails(entry, startRelative, requestID),

src/ts/transformers/har-tabs.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { Entry } from "har-format";
22
import { escapeHtml } from "../helpers/parse";
33
import {
4+
KvTuple,
45
RequestType,
56
TabRenderer,
67
WaterfallEntryIndicator,
78
WaterfallEntryTab,
89
} from "../typing/waterfall";
9-
import { getKeys, KvTuple } from "./extract-details-keys";
10+
import { getKeys } from "./extract-details-keys";
1011
import { makeDefinitionList } from "./helpers";
1112

1213
/**

src/ts/transformers/helpers.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import { escapeHtml } from "../helpers/parse";
44
import { RequestType } from "../typing/waterfall";
55
import {
66
Icon,
7+
KvTuple,
78
TimingType,
89
WaterfallEntry,
910
WaterfallEntryTab,
1011
WaterfallEntryTiming,
1112
WaterfallResponseDetails,
1213
} from "../typing/waterfall";
1314
import { makeIcon } from "../waterfall/row/svg-indicators";
14-
import { KvTuple } from "./extract-details-keys";
1515

1616
/** render a dl */
1717
export function makeDefinitionList(dlKeyValues: KvTuple[], addClass: boolean = false) {
@@ -155,3 +155,23 @@ export function makeMimeTypeIcon(status: number,
155155
return makeIcon(requestType, requestType);
156156
}
157157
}
158+
159+
/**
160+
* Flattens out a second level of `KvTuple` nesting (and removed empty and `undefined` entries)
161+
*
162+
* @param nestedKvPairs - nested `KvTuple`s (possibly sub-nested)
163+
*/
164+
export const flattenKvTuple = (nestedKvPairs: Array<(KvTuple | KvTuple[])>): KvTuple[] => {
165+
let returnKv: KvTuple[] = [];
166+
nestedKvPairs.forEach((maybeKv) => {
167+
if (maybeKv === undefined || maybeKv.length === 0) {
168+
return;
169+
}
170+
if (Array.isArray(maybeKv[0])) {
171+
returnKv.push(...(maybeKv as KvTuple[]));
172+
return;
173+
}
174+
returnKv.push(maybeKv as KvTuple);
175+
});
176+
return returnKv;
177+
};

src/ts/typing/waterfall.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,6 @@ export interface Icon {
120120
/** width of icon in px */
121121
width: number;
122122
}
123+
124+
/** Key/Value pair in array `["key", "value"]` */
125+
export type KvTuple = [string, string];

0 commit comments

Comments
 (0)