1- import { API } from '@sentry/core' ;
1+ import { API , logger } from '@sentry/core' ;
22import { getCurrentHub } from '@sentry/hub' ;
33import { Integration , Severity } from '@sentry/types' ;
44import { isFunction , isString } from '@sentry/utils/is' ;
55import { getGlobalObject , parseUrl } from '@sentry/utils/misc' ;
6- import { fill } from '@sentry/utils/object' ;
6+ import { deserialize , fill } from '@sentry/utils/object' ;
77import { safeJoin } from '@sentry/utils/string' ;
8- import { supportsFetch , supportsHistory } from '@sentry/utils/supports' ;
8+ import { supportsBeacon , supportsFetch , supportsHistory } from '@sentry/utils/supports' ;
99import { BrowserOptions } from '../backend' ;
1010import { breadcrumbEventHandler , keypressEventHandler , wrap } from './helpers' ;
1111
@@ -27,6 +27,24 @@ export interface SentryWrappedXMLHttpRequest extends XMLHttpRequest {
2727 } ;
2828}
2929
30+ /** JSDoc */
31+ function addSentryBreadcrumb ( serializedData : string ) : void {
32+ // There's always something that can go wrong with deserialization...
33+ try {
34+ const data : { [ key : string ] : any } = deserialize ( serializedData ) ;
35+ const exception = data . exception && data . exception . values && data . exception . values [ 0 ] ;
36+
37+ getCurrentHub ( ) . addBreadcrumb ( {
38+ category : 'sentry' ,
39+ event_id : data . event_id ,
40+ level : data . level || Severity . fromString ( 'error' ) ,
41+ message : exception ? `${ exception . type ? `${ exception . type } : ` : '' } ${ exception . value } ` : data . message ,
42+ } ) ;
43+ } catch ( _oO ) {
44+ logger . error ( 'Error while adding sentry type breadcrumb' ) ;
45+ }
46+ }
47+
3048/** Default Breadcrumbs instrumentations */
3149export class Breadcrumbs implements Integration {
3250 /**
@@ -39,20 +57,66 @@ export class Breadcrumbs implements Integration {
3957 */
4058 public constructor (
4159 private readonly config : {
60+ beacon ?: boolean ;
4261 console ?: boolean ;
4362 dom ?: boolean ;
4463 fetch ?: boolean ;
4564 history ?: boolean ;
65+ sentry ?: boolean ;
4666 xhr ?: boolean ;
4767 } = {
68+ beacon : true ,
4869 console : true ,
4970 dom : true ,
5071 fetch : true ,
5172 history : true ,
73+ sentry : true ,
5274 xhr : true ,
5375 } ,
5476 ) { }
5577
78+ /** JSDoc */
79+ private instrumentBeacon ( options : { filterUrl ?: string } ) : void {
80+ if ( ! supportsBeacon ( ) ) {
81+ return ;
82+ }
83+
84+ /** JSDoc */
85+ function beaconReplacementFunction ( originalBeaconFunction : ( ) => void ) : ( ) => void {
86+ return function ( this : History , ...args : any [ ] ) : void {
87+ const url = args [ 0 ] ;
88+ const data = args [ 1 ] ;
89+ // If the browser successfully queues the request for delivery, the method returns "true" and returns "false" otherwise.
90+ // https://developer.mozilla.org/en-US/docs/Web/API/Beacon_API/Using_the_Beacon_API
91+ const result = originalBeaconFunction . apply ( this , args ) ;
92+
93+ // if Sentry key appears in URL, don't capture it as a request
94+ // but rather as our own 'sentry' type breadcrumb
95+ if ( options . filterUrl && url . includes ( options . filterUrl ) ) {
96+ addSentryBreadcrumb ( data ) ;
97+ return result ;
98+ }
99+
100+ // What is wrong with you TypeScript...
101+ const crumb = ( {
102+ category : 'beacon' ,
103+ data,
104+ type : 'http' ,
105+ } as any ) as { [ key : string ] : any } ;
106+
107+ if ( ! result ) {
108+ crumb . level = Severity . Error ;
109+ }
110+
111+ getCurrentHub ( ) . addBreadcrumb ( crumb ) ;
112+
113+ return result ;
114+ } ;
115+ }
116+
117+ fill ( global . navigator , 'sendBeacon' , beaconReplacementFunction ) ;
118+ }
119+
56120 /** JSDoc */
57121 private instrumentConsole ( ) : void {
58122 if ( ! ( 'console' in global ) ) {
@@ -133,15 +197,19 @@ export class Breadcrumbs implements Integration {
133197 url = String ( fetchInput ) ;
134198 }
135199
136- // if Sentry key appears in URL, don't capture, as it's our own request
137- if ( options . filterUrl && url . includes ( options . filterUrl ) ) {
138- return originalFetch . apply ( global , args ) ;
139- }
140-
141200 if ( args [ 1 ] && args [ 1 ] . method ) {
142201 method = args [ 1 ] . method ;
143202 }
144203
204+ // if Sentry key appears in URL, don't capture it as a request
205+ // but rather as our own 'sentry' type breadcrumb
206+ if ( options . filterUrl && url . includes ( options . filterUrl ) ) {
207+ if ( method === 'POST' && args [ 1 ] && args [ 1 ] . body ) {
208+ addSentryBreadcrumb ( args [ 1 ] . body ) ;
209+ }
210+ return originalFetch . apply ( global , args ) ;
211+ }
212+
145213 const fetchData : {
146214 method : string ;
147215 url : string ;
@@ -245,6 +313,7 @@ export class Breadcrumbs implements Integration {
245313 fill ( global . history , 'pushState' , historyReplacementFunction ) ;
246314 fill ( global . history , 'replaceState' , historyReplacementFunction ) ;
247315 }
316+
248317 /** JSDoc */
249318 private instrumentXHR ( options : { filterUrl ?: string } ) : void {
250319 if ( ! ( 'XMLHttpRequest' in global ) ) {
@@ -277,12 +346,14 @@ export class Breadcrumbs implements Integration {
277346 originalOpen =>
278347 function ( this : SentryWrappedXMLHttpRequest , ...args : any [ ] ) : void {
279348 const url = args [ 1 ] ;
280- // if Sentry key appears in URL, don't capture, as it's our own request
281- if ( isString ( url ) && ( options . filterUrl && ! url . includes ( options . filterUrl ) ) ) {
282- this . __sentry_xhr__ = {
283- method : args [ 0 ] ,
284- url : args [ 1 ] ,
285- } ;
349+ this . __sentry_xhr__ = {
350+ method : args [ 0 ] ,
351+ url : args [ 1 ] ,
352+ } ;
353+ // if Sentry key appears in URL, don't capture it as a request
354+ // but rather as our own 'sentry' type breadcrumb
355+ if ( isString ( url ) && ( options . filterUrl && url . includes ( options . filterUrl ) ) ) {
356+ this . __sentry_own_request__ = true ;
286357 }
287358 return originalOpen . apply ( this , args ) ;
288359 } ,
@@ -295,13 +366,22 @@ export class Breadcrumbs implements Integration {
295366 function ( this : SentryWrappedXMLHttpRequest , ...args : any [ ] ) : void {
296367 const xhr = this ; // tslint:disable-line:no-this-assignment
297368
369+ if ( xhr . __sentry_own_request__ ) {
370+ addSentryBreadcrumb ( args [ 0 ] ) ;
371+ }
372+
298373 /** JSDoc */
299374 function onreadystatechangeHandler ( ) : void {
300- if ( xhr . __sentry_xhr__ && xhr . readyState === 4 ) {
375+ if ( xhr . readyState === 4 ) {
376+ if ( xhr . __sentry_own_request__ ) {
377+ return ;
378+ }
301379 try {
302380 // touching statusCode in some platforms throws
303381 // an exception
304- xhr . __sentry_xhr__ . status_code = xhr . status ;
382+ if ( xhr . __sentry_xhr__ ) {
383+ xhr . __sentry_xhr__ . status_code = xhr . status ;
384+ }
305385 } catch ( e ) {
306386 /* do nothing */
307387 }
@@ -368,6 +448,9 @@ export class Breadcrumbs implements Integration {
368448 if ( this . config . fetch ) {
369449 this . instrumentFetch ( { filterUrl } ) ;
370450 }
451+ if ( this . config . beacon ) {
452+ this . instrumentBeacon ( { filterUrl } ) ;
453+ }
371454 if ( this . config . history ) {
372455 this . instrumentHistory ( ) ;
373456 }
0 commit comments