Skip to content

Commit c224f9c

Browse files
authored
fix(tracing): ignore the xhr/fetch response if its request is not being tracked (#4428)
1 parent 6a10759 commit c224f9c

File tree

3 files changed

+68
-23
lines changed

3 files changed

+68
-23
lines changed

packages/tracing/src/browser/request.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,11 @@ export function fetchCallback(
141141
return;
142142
}
143143

144-
if (handlerData.endTimestamp && handlerData.fetchData.__span) {
145-
const span = spans[handlerData.fetchData.__span];
144+
if (handlerData.endTimestamp) {
145+
const spanId = handlerData.fetchData.__span;
146+
if (!spanId) return;
147+
148+
const span = spans[spanId];
146149
if (span) {
147150
if (handlerData.response) {
148151
// TODO (kmclb) remove this once types PR goes through
@@ -154,7 +157,7 @@ export function fetchCallback(
154157
span.finish();
155158

156159
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
157-
delete spans[handlerData.fetchData.__span];
160+
delete spans[spanId];
158161
}
159162
return;
160163
}
@@ -216,14 +219,17 @@ export function xhrCallback(
216219
const xhr = handlerData.xhr.__sentry_xhr__;
217220

218221
// check first if the request has finished and is tracked by an existing span which should now end
219-
if (handlerData.endTimestamp && handlerData.xhr.__sentry_xhr_span_id__) {
220-
const span = spans[handlerData.xhr.__sentry_xhr_span_id__];
222+
if (handlerData.endTimestamp) {
223+
const spanId = handlerData.xhr.__sentry_xhr_span_id__;
224+
if (!spanId) return;
225+
226+
const span = spans[spanId];
221227
if (span) {
222228
span.setHttpStatus(xhr.status_code);
223229
span.finish();
224230

225231
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
226-
delete spans[handlerData.xhr.__sentry_xhr_span_id__];
232+
delete spans[spanId];
227233
}
228234
return;
229235
}

packages/tracing/test/browser/request.test.ts

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,12 @@ describe('callbacks', () => {
4848
let transaction: Transaction;
4949
const alwaysCreateSpan = () => true;
5050
const neverCreateSpan = () => false;
51+
const startTimestamp = 1356996072000;
52+
const endTimestamp = 1356996072000;
5153
const fetchHandlerData: FetchData = {
5254
args: ['http://dogs.are.great/', {}],
5355
fetchData: { url: 'http://dogs.are.great/', method: 'GET' },
54-
startTimestamp: 1356996072000,
56+
startTimestamp,
5557
};
5658
const xhrHandlerData: XHRData = {
5759
xhr: {
@@ -66,9 +68,8 @@ describe('callbacks', () => {
6668
// setRequestHeader: XMLHttpRequest.prototype.setRequestHeader,
6769
setRequestHeader,
6870
},
69-
startTimestamp: 1353501072000,
71+
startTimestamp,
7072
};
71-
const endTimestamp = 1356996072000;
7273

7374
beforeAll(() => {
7475
hub = new Hub(new BrowserClient({ tracesSampleRate: 1 }));
@@ -173,6 +174,23 @@ describe('callbacks', () => {
173174
expect(newSpan!.status).toBe(spanStatusfromHttpCode(404));
174175
});
175176

177+
it('ignores response with no associated span', () => {
178+
// the request might be missed somehow. E.g. if it was sent before tracing gets enabled.
179+
180+
const postRequestFetchHandlerData = {
181+
...fetchHandlerData,
182+
endTimestamp,
183+
response: { status: 404 } as Response,
184+
};
185+
186+
// in that case, the response coming back will be ignored
187+
fetchCallback(postRequestFetchHandlerData, alwaysCreateSpan, {});
188+
189+
const newSpan = transaction.spanRecorder?.spans[1];
190+
191+
expect(newSpan).toBeUndefined();
192+
});
193+
176194
it('adds sentry-trace header to fetch requests', () => {
177195
// TODO
178196
});
@@ -263,5 +281,26 @@ describe('callbacks', () => {
263281

264282
expect(newSpan!.status).toBe(spanStatusfromHttpCode(404));
265283
});
284+
285+
it('ignores response with no associated span', () => {
286+
// the request might be missed somehow. E.g. if it was sent before tracing gets enabled.
287+
288+
const postRequestXHRHandlerData = {
289+
...{
290+
xhr: {
291+
__sentry_xhr__: xhrHandlerData.xhr.__sentry_xhr__,
292+
},
293+
},
294+
startTimestamp,
295+
endTimestamp,
296+
};
297+
298+
// in that case, the response coming back will be ignored
299+
xhrCallback(postRequestXHRHandlerData, alwaysCreateSpan, {});
300+
301+
const newSpan = transaction.spanRecorder?.spans[1];
302+
303+
expect(newSpan).toBeUndefined();
304+
});
266305
});
267306
});

packages/tracing/test/hub.test.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ describe('Hub', () => {
359359
// TODO the way we dig out the headers to test them doesn't work on Node < 10
360360
testOnlyIfNodeVersionAtLeast(10)(
361361
'should propagate positive sampling decision to child transactions in XHR header',
362-
() => {
362+
async () => {
363363
const hub = new Hub(
364364
new BrowserClient({
365365
dsn: 'https://[email protected]/1121',
@@ -375,12 +375,12 @@ describe('Hub', () => {
375375
});
376376

377377
const request = new XMLHttpRequest();
378-
request.open('GET', '/chase-partners');
379-
380-
// mock a response having been received successfully (we have to do it in this roundabout way because readyState
381-
// is readonly and changing it doesn't trigger a readystatechange event)
382-
Object.defineProperty(request, 'readyState', { value: 4 });
383-
request.dispatchEvent(new Event('readystatechange'));
378+
await new Promise(resolve => {
379+
request.timeout = 1;
380+
request.onloadend = request.ontimeout = resolve;
381+
request.open('GET', '/chase-partners');
382+
request.send('');
383+
});
384384

385385
// this looks weird, it's true, but it's really just `request.impl.flag.requestHeaders` - it's just that the
386386
// `impl` key is a symbol rather than a string, and therefore needs to be referred to by reference rather than
@@ -401,7 +401,7 @@ describe('Hub', () => {
401401
// TODO the way we dig out the headers to test them doesn't work on Node < 10
402402
testOnlyIfNodeVersionAtLeast(10)(
403403
'should propagate negative sampling decision to child transactions in XHR header',
404-
() => {
404+
async () => {
405405
const hub = new Hub(
406406
new BrowserClient({
407407
dsn: 'https://[email protected]/1121',
@@ -417,12 +417,12 @@ describe('Hub', () => {
417417
});
418418

419419
const request = new XMLHttpRequest();
420-
request.open('GET', '/chase-partners');
421-
422-
// mock a response having been received successfully (we have to do it in this roundabout way because readyState
423-
// is readonly and changing it doesn't trigger a readystatechange event)
424-
Object.defineProperty(request, 'readyState', { value: 4 });
425-
request.dispatchEvent(new Event('readystatechange'));
420+
await new Promise(resolve => {
421+
request.timeout = 1;
422+
request.onloadend = request.ontimeout = resolve;
423+
request.open('GET', '/chase-partners');
424+
request.send('');
425+
});
426426

427427
// this looks weird, it's true, but it's really just `request.impl.flag.requestHeaders` - it's just that the
428428
// `impl` key is a symbol rather than a string, and therefore needs to be referred to by reference rather than

0 commit comments

Comments
 (0)