1
- import type { Client , HandlerDataXhr , SentryWrappedXMLHttpRequest , Span , WebFetchHeaders } from '@sentry/core' ;
1
+ import type {
2
+ Client ,
3
+ HandlerDataXhr ,
4
+ RequestHookInfo ,
5
+ ResponseHookInfo ,
6
+ SentryWrappedXMLHttpRequest ,
7
+ Span ,
8
+ } from '@sentry/core' ;
2
9
import {
3
10
addFetchEndInstrumentationHandler ,
4
11
addFetchInstrumentationHandler ,
@@ -26,7 +33,7 @@ import {
26
33
SENTRY_XHR_DATA_KEY ,
27
34
} from '@sentry-internal/browser-utils' ;
28
35
import type { BrowserClient } from '../client' ;
29
- import { WINDOW } from '../helpers ' ;
36
+ import { baggageHeaderHasSentryValues , createHeadersSafely , getFullURL , isPerformanceResourceTiming } from './utils ' ;
30
37
31
38
/** Options for Request Instrumentation */
32
39
export interface RequestInstrumentationOptions {
@@ -102,7 +109,12 @@ export interface RequestInstrumentationOptions {
102
109
/**
103
110
* Is called when spans are started for outgoing requests.
104
111
*/
105
- onRequestSpanStart ?( span : Span , requestInformation : { headers ?: WebFetchHeaders } ) : void ;
112
+ onRequestSpanStart ?( span : Span , requestInformation : RequestHookInfo ) : void ;
113
+
114
+ /**
115
+ * Is called when spans end for outgoing requests, providing access to response headers.
116
+ */
117
+ onRequestSpanEnd ?( span : Span , responseInformation : ResponseHookInfo ) : void ;
106
118
}
107
119
108
120
const responseToSpanId = new WeakMap < object , string > ( ) ;
@@ -125,6 +137,7 @@ export function instrumentOutgoingRequests(client: Client, _options?: Partial<Re
125
137
enableHTTPTimings,
126
138
tracePropagationTargets,
127
139
onRequestSpanStart,
140
+ onRequestSpanEnd,
128
141
} = {
129
142
...defaultRequestInstrumentationOptions ,
130
143
..._options ,
@@ -171,6 +184,7 @@ export function instrumentOutgoingRequests(client: Client, _options?: Partial<Re
171
184
addFetchInstrumentationHandler ( handlerData => {
172
185
const createdSpan = instrumentFetchRequest ( handlerData , shouldCreateSpan , shouldAttachHeadersWithTargets , spans , {
173
186
propagateTraceparent,
187
+ onRequestSpanEnd,
174
188
} ) ;
175
189
176
190
if ( handlerData . response && handlerData . fetchData . __span ) {
@@ -205,34 +219,22 @@ export function instrumentOutgoingRequests(client: Client, _options?: Partial<Re
205
219
shouldAttachHeadersWithTargets ,
206
220
spans ,
207
221
propagateTraceparent ,
222
+ onRequestSpanEnd ,
208
223
) ;
209
224
210
225
if ( createdSpan ) {
211
226
if ( enableHTTPTimings ) {
212
227
addHTTPTimings ( createdSpan ) ;
213
228
}
214
229
215
- let headers ;
216
- try {
217
- headers = new Headers ( handlerData . xhr . __sentry_xhr_v3__ ?. request_headers ) ;
218
- } catch {
219
- // noop
220
- }
221
- onRequestSpanStart ?.( createdSpan , { headers } ) ;
230
+ onRequestSpanStart ?.( createdSpan , {
231
+ headers : createHeadersSafely ( handlerData . xhr . __sentry_xhr_v3__ ?. request_headers ) ,
232
+ } ) ;
222
233
}
223
234
} ) ;
224
235
}
225
236
}
226
237
227
- function isPerformanceResourceTiming ( entry : PerformanceEntry ) : entry is PerformanceResourceTiming {
228
- return (
229
- entry . entryType === 'resource' &&
230
- 'initiatorType' in entry &&
231
- typeof ( entry as PerformanceResourceTiming ) . nextHopProtocol === 'string' &&
232
- ( entry . initiatorType === 'fetch' || entry . initiatorType === 'xmlhttprequest' )
233
- ) ;
234
- }
235
-
236
238
/**
237
239
* Creates a temporary observer to listen to the next fetch/xhr resourcing timings,
238
240
* so that when timings hit their per-browser limit they don't need to be removed.
@@ -315,6 +317,7 @@ function xhrCallback(
315
317
shouldAttachHeaders : ( url : string ) => boolean ,
316
318
spans : Record < string , Span > ,
317
319
propagateTraceparent ?: boolean ,
320
+ onRequestSpanEnd ?: RequestInstrumentationOptions [ 'onRequestSpanEnd' ] ,
318
321
) : Span | undefined {
319
322
const xhr = handlerData . xhr ;
320
323
const sentryXhrData = xhr ?. [ SENTRY_XHR_DATA_KEY ] ;
@@ -337,6 +340,10 @@ function xhrCallback(
337
340
setHttpStatus ( span , sentryXhrData . status_code ) ;
338
341
span . end ( ) ;
339
342
343
+ onRequestSpanEnd ?.( span , {
344
+ headers : createHeadersSafely ( sentryXhrData . request_headers ) ,
345
+ } ) ;
346
+
340
347
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
341
348
delete spans [ spanId ] ;
342
349
}
@@ -438,18 +445,3 @@ function setHeaderOnXhr(
438
445
// Error: InvalidStateError: Failed to execute 'setRequestHeader' on 'XMLHttpRequest': The object's state must be OPENED.
439
446
}
440
447
}
441
-
442
- function baggageHeaderHasSentryValues ( baggageHeader : string ) : boolean {
443
- return baggageHeader . split ( ',' ) . some ( value => value . trim ( ) . startsWith ( 'sentry-' ) ) ;
444
- }
445
-
446
- function getFullURL ( url : string ) : string | undefined {
447
- try {
448
- // By adding a base URL to new URL(), this will also work for relative urls
449
- // If `url` is a full URL, the base URL is ignored anyhow
450
- const parsed = new URL ( url , WINDOW . location . origin ) ;
451
- return parsed . href ;
452
- } catch {
453
- return undefined ;
454
- }
455
- }
0 commit comments