Skip to content

Commit 85f9bd0

Browse files
authored
fix(browser): Guard against undefined nextHopProtocol (#16806)
fixes #16804 Although `PerformanceResourceTiming.nextHopProtocol` is available in all the [browsers we support](https://docs.sentry.io/platforms/javascript/troubleshooting/supported-browsers/) in v9, I think adding an extra guard is reasonable to make it easier for older browser support (it's hard to polyfill some of the performance apis). This PR just adds a condition `if (resourceTiming.nextHopProtocol != undefined)`. Used cursor to generate some tests for the `resourceTimingToSpanAttributes` I extracted out.
1 parent 2911b4a commit 85f9bd0

File tree

3 files changed

+581
-33
lines changed

3 files changed

+581
-33
lines changed

packages/browser/src/tracing/request.ts

Lines changed: 3 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import type { Client, HandlerDataXhr, SentryWrappedXMLHttpRequest, Span, WebFetc
22
import {
33
addFetchEndInstrumentationHandler,
44
addFetchInstrumentationHandler,
5-
browserPerformanceTimeOrigin,
65
getActiveSpan,
76
getClient,
87
getLocationHref,
@@ -23,10 +22,10 @@ import type { XhrHint } from '@sentry-internal/browser-utils';
2322
import {
2423
addPerformanceInstrumentationHandler,
2524
addXhrInstrumentationHandler,
26-
extractNetworkProtocol,
2725
SENTRY_XHR_DATA_KEY,
2826
} from '@sentry-internal/browser-utils';
2927
import { WINDOW } from '../helpers';
28+
import { resourceTimingToSpanAttributes } from './resource-timing';
3029

3130
/** Options for Request Instrumentation */
3231
export interface RequestInstrumentationOptions {
@@ -238,8 +237,8 @@ function addHTTPTimings(span: Span): void {
238237
const cleanup = addPerformanceInstrumentationHandler('resource', ({ entries }) => {
239238
entries.forEach(entry => {
240239
if (isPerformanceResourceTiming(entry) && entry.name.endsWith(url)) {
241-
const spanData = resourceTimingEntryToSpanData(entry);
242-
spanData.forEach(data => span.setAttribute(...data));
240+
const spanAttributes = resourceTimingToSpanAttributes(entry);
241+
spanAttributes.forEach(attributeArray => span.setAttribute(...attributeArray));
243242
// In the next tick, clean this handler up
244243
// We have to wait here because otherwise this cleans itself up before it is fully done
245244
setTimeout(cleanup);
@@ -248,35 +247,6 @@ function addHTTPTimings(span: Span): void {
248247
});
249248
}
250249

251-
function getAbsoluteTime(time: number = 0): number {
252-
return ((browserPerformanceTimeOrigin() || performance.timeOrigin) + time) / 1000;
253-
}
254-
255-
function resourceTimingEntryToSpanData(resourceTiming: PerformanceResourceTiming): [string, string | number][] {
256-
const { name, version } = extractNetworkProtocol(resourceTiming.nextHopProtocol);
257-
258-
const timingSpanData: [string, string | number][] = [];
259-
260-
timingSpanData.push(['network.protocol.version', version], ['network.protocol.name', name]);
261-
262-
if (!browserPerformanceTimeOrigin()) {
263-
return timingSpanData;
264-
}
265-
return [
266-
...timingSpanData,
267-
['http.request.redirect_start', getAbsoluteTime(resourceTiming.redirectStart)],
268-
['http.request.fetch_start', getAbsoluteTime(resourceTiming.fetchStart)],
269-
['http.request.domain_lookup_start', getAbsoluteTime(resourceTiming.domainLookupStart)],
270-
['http.request.domain_lookup_end', getAbsoluteTime(resourceTiming.domainLookupEnd)],
271-
['http.request.connect_start', getAbsoluteTime(resourceTiming.connectStart)],
272-
['http.request.secure_connection_start', getAbsoluteTime(resourceTiming.secureConnectionStart)],
273-
['http.request.connection_end', getAbsoluteTime(resourceTiming.connectEnd)],
274-
['http.request.request_start', getAbsoluteTime(resourceTiming.requestStart)],
275-
['http.request.response_start', getAbsoluteTime(resourceTiming.responseStart)],
276-
['http.request.response_end', getAbsoluteTime(resourceTiming.responseEnd)],
277-
];
278-
}
279-
280250
/**
281251
* A function that determines whether to attach tracing headers to a request.
282252
* We only export this function for testing purposes.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import type { Span } from '@sentry/core';
2+
import { browserPerformanceTimeOrigin } from '@sentry/core';
3+
import { extractNetworkProtocol } from '@sentry-internal/browser-utils';
4+
5+
function getAbsoluteTime(time: number = 0): number {
6+
return ((browserPerformanceTimeOrigin() || performance.timeOrigin) + time) / 1000;
7+
}
8+
9+
/**
10+
* Converts a PerformanceResourceTiming entry to span data for the resource span.
11+
*
12+
* @param resourceTiming
13+
* @returns An array where the first element is the attribute name and the second element is the attribute value.
14+
*/
15+
export function resourceTimingToSpanAttributes(
16+
resourceTiming: PerformanceResourceTiming,
17+
): Array<Parameters<Span['setAttribute']>> {
18+
const timingSpanData: Array<Parameters<Span['setAttribute']>> = [];
19+
// Checking for only `undefined` and `null` is intentional because it's
20+
// valid for `nextHopProtocol` to be an empty string.
21+
if (resourceTiming.nextHopProtocol != undefined) {
22+
const { name, version } = extractNetworkProtocol(resourceTiming.nextHopProtocol);
23+
timingSpanData.push(['network.protocol.version', version], ['network.protocol.name', name]);
24+
}
25+
if (!browserPerformanceTimeOrigin()) {
26+
return timingSpanData;
27+
}
28+
return [
29+
...timingSpanData,
30+
['http.request.redirect_start', getAbsoluteTime(resourceTiming.redirectStart)],
31+
['http.request.fetch_start', getAbsoluteTime(resourceTiming.fetchStart)],
32+
['http.request.domain_lookup_start', getAbsoluteTime(resourceTiming.domainLookupStart)],
33+
['http.request.domain_lookup_end', getAbsoluteTime(resourceTiming.domainLookupEnd)],
34+
['http.request.connect_start', getAbsoluteTime(resourceTiming.connectStart)],
35+
['http.request.secure_connection_start', getAbsoluteTime(resourceTiming.secureConnectionStart)],
36+
['http.request.connection_end', getAbsoluteTime(resourceTiming.connectEnd)],
37+
['http.request.request_start', getAbsoluteTime(resourceTiming.requestStart)],
38+
['http.request.response_start', getAbsoluteTime(resourceTiming.responseStart)],
39+
['http.request.response_end', getAbsoluteTime(resourceTiming.responseEnd)],
40+
];
41+
}

0 commit comments

Comments
 (0)