@@ -9,7 +9,7 @@ import type {
99 WrappedFunction ,
1010} from '@sentry/types' ;
1111
12- import { isInstanceOf , isString } from './is' ;
12+ import { isString } from './is' ;
1313import { CONSOLE_LEVELS , logger } from './logger' ;
1414import { fill } from './object' ;
1515import { getFunctionName } from './stacktrace' ;
@@ -142,11 +142,13 @@ function instrumentFetch(): void {
142142
143143 fill ( WINDOW , 'fetch' , function ( originalFetch : ( ) => void ) : ( ) => void {
144144 return function ( ...args : any [ ] ) : void {
145+ const { method, url } = parseFetchArgs ( args ) ;
146+
145147 const handlerData : HandlerDataFetch = {
146148 args,
147149 fetchData : {
148- method : getFetchMethod ( args ) ,
149- url : getFetchUrl ( args ) ,
150+ method,
151+ url,
150152 } ,
151153 startTimestamp : Date . now ( ) ,
152154 } ;
@@ -181,29 +183,56 @@ function instrumentFetch(): void {
181183 } ) ;
182184}
183185
184- /* eslint-disable @typescript-eslint/no-unsafe-member-access */
185- /** Extract `method` from fetch call arguments */
186- function getFetchMethod ( fetchArgs : any [ ] = [ ] ) : string {
187- if ( 'Request' in WINDOW && isInstanceOf ( fetchArgs [ 0 ] , Request ) && fetchArgs [ 0 ] . method ) {
188- return String ( fetchArgs [ 0 ] . method ) . toUpperCase ( ) ;
186+ function hasProp < T extends string > ( obj : unknown , prop : T ) : obj is Record < string , string > {
187+ return ! ! obj && typeof obj === 'object' && ! ! ( obj as Record < string , string > ) [ prop ] ;
188+ }
189+
190+ type FetchResource = string | { toString ( ) : string } | { url : string } ;
191+
192+ function getUrlFromResource ( resource : FetchResource ) : string {
193+ if ( typeof resource === 'string' ) {
194+ return resource ;
195+ }
196+
197+ if ( ! resource ) {
198+ return '' ;
189199 }
190- if ( fetchArgs [ 1 ] && fetchArgs [ 1 ] . method ) {
191- return String ( fetchArgs [ 1 ] . method ) . toUpperCase ( ) ;
200+
201+ if ( hasProp ( resource , 'url' ) ) {
202+ return resource . url ;
203+ }
204+
205+ if ( resource . toString ) {
206+ return resource . toString ( ) ;
192207 }
193- return 'GET' ;
208+
209+ return '' ;
194210}
195211
196- /** Extract `url` from fetch call arguments */
197- function getFetchUrl ( fetchArgs : any [ ] = [ ] ) : string {
198- if ( typeof fetchArgs [ 0 ] === 'string' ) {
199- return fetchArgs [ 0 ] ;
212+ /**
213+ * Exported only for tests.
214+ * @hidden
215+ * */
216+ export function parseFetchArgs ( fetchArgs : unknown [ ] ) : { method : string ; url : string } {
217+ if ( fetchArgs . length === 0 ) {
218+ return { method : 'GET' , url : '' } ;
200219 }
201- if ( 'Request' in WINDOW && isInstanceOf ( fetchArgs [ 0 ] , Request ) ) {
202- return fetchArgs [ 0 ] . url ;
220+
221+ if ( fetchArgs . length === 2 ) {
222+ const [ url , options ] = fetchArgs as [ FetchResource , object ] ;
223+
224+ return {
225+ url : getUrlFromResource ( url ) ,
226+ method : hasProp ( options , 'method' ) ? String ( options . method ) . toUpperCase ( ) : 'GET' ,
227+ } ;
203228 }
204- return String ( fetchArgs [ 0 ] ) ;
229+
230+ const arg = fetchArgs [ 0 ] ;
231+ return {
232+ url : getUrlFromResource ( arg as FetchResource ) ,
233+ method : hasProp ( arg , 'method' ) ? String ( arg . method ) . toUpperCase ( ) : 'GET' ,
234+ } ;
205235}
206- /* eslint-enable @typescript-eslint/no-unsafe-member-access */
207236
208237/** JSDoc */
209238function instrumentXHR ( ) : void {
@@ -220,6 +249,7 @@ function instrumentXHR(): void {
220249 // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
221250 method : isString ( args [ 0 ] ) ? args [ 0 ] . toUpperCase ( ) : args [ 0 ] ,
222251 url : args [ 1 ] ,
252+ request_headers : { } ,
223253 } ) ;
224254
225255 // if Sentry key appears in URL, don't capture it as a request
@@ -265,6 +295,23 @@ function instrumentXHR(): void {
265295 this . addEventListener ( 'readystatechange' , onreadystatechangeHandler ) ;
266296 }
267297
298+ // Intercepting `setRequestHeader` to access the request headers of XHR instance.
299+ // This will only work for user/library defined headers, not for the default/browser-assigned headers.
300+ // Request cookies are also unavailable for XHR, as `Cookie` header can't be defined by `setRequestHeader`.
301+ fill ( this , 'setRequestHeader' , function ( original : WrappedFunction ) : Function {
302+ return function ( this : SentryWrappedXMLHttpRequest , ...setRequestHeaderArgs : unknown [ ] ) : void {
303+ const [ header , value ] = setRequestHeaderArgs as [ string , string ] ;
304+
305+ const xhrInfo = this . __sentry_xhr__ ;
306+
307+ if ( xhrInfo ) {
308+ xhrInfo . request_headers [ header ] = value ;
309+ }
310+
311+ return original . apply ( this , setRequestHeaderArgs ) ;
312+ } ;
313+ } ) ;
314+
268315 return originalOpen . apply ( this , args ) ;
269316 } ;
270317 } ) ;
0 commit comments