1
- import { API } from '@sentry/core' ;
1
+ import { API , logger } from '@sentry/core' ;
2
2
import { getCurrentHub } from '@sentry/hub' ;
3
3
import { Integration , Severity } from '@sentry/types' ;
4
4
import { isFunction , isString } from '@sentry/utils/is' ;
5
5
import { getGlobalObject , parseUrl } from '@sentry/utils/misc' ;
6
- import { fill } from '@sentry/utils/object' ;
6
+ import { deserialize , fill } from '@sentry/utils/object' ;
7
7
import { safeJoin } from '@sentry/utils/string' ;
8
- import { supportsFetch , supportsHistory } from '@sentry/utils/supports' ;
8
+ import { supportsBeacon , supportsFetch , supportsHistory } from '@sentry/utils/supports' ;
9
9
import { BrowserOptions } from '../backend' ;
10
10
import { breadcrumbEventHandler , keypressEventHandler , wrap } from './helpers' ;
11
11
@@ -27,6 +27,24 @@ export interface SentryWrappedXMLHttpRequest extends XMLHttpRequest {
27
27
} ;
28
28
}
29
29
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
+
30
48
/** Default Breadcrumbs instrumentations */
31
49
export class Breadcrumbs implements Integration {
32
50
/**
@@ -39,20 +57,66 @@ export class Breadcrumbs implements Integration {
39
57
*/
40
58
public constructor (
41
59
private readonly config : {
60
+ beacon ?: boolean ;
42
61
console ?: boolean ;
43
62
dom ?: boolean ;
44
63
fetch ?: boolean ;
45
64
history ?: boolean ;
65
+ sentry ?: boolean ;
46
66
xhr ?: boolean ;
47
67
} = {
68
+ beacon : true ,
48
69
console : true ,
49
70
dom : true ,
50
71
fetch : true ,
51
72
history : true ,
73
+ sentry : true ,
52
74
xhr : true ,
53
75
} ,
54
76
) { }
55
77
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
+
56
120
/** JSDoc */
57
121
private instrumentConsole ( ) : void {
58
122
if ( ! ( 'console' in global ) ) {
@@ -133,15 +197,19 @@ export class Breadcrumbs implements Integration {
133
197
url = String ( fetchInput ) ;
134
198
}
135
199
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
-
141
200
if ( args [ 1 ] && args [ 1 ] . method ) {
142
201
method = args [ 1 ] . method ;
143
202
}
144
203
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
+
145
213
const fetchData : {
146
214
method : string ;
147
215
url : string ;
@@ -245,6 +313,7 @@ export class Breadcrumbs implements Integration {
245
313
fill ( global . history , 'pushState' , historyReplacementFunction ) ;
246
314
fill ( global . history , 'replaceState' , historyReplacementFunction ) ;
247
315
}
316
+
248
317
/** JSDoc */
249
318
private instrumentXHR ( options : { filterUrl ?: string } ) : void {
250
319
if ( ! ( 'XMLHttpRequest' in global ) ) {
@@ -277,12 +346,14 @@ export class Breadcrumbs implements Integration {
277
346
originalOpen =>
278
347
function ( this : SentryWrappedXMLHttpRequest , ...args : any [ ] ) : void {
279
348
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 ;
286
357
}
287
358
return originalOpen . apply ( this , args ) ;
288
359
} ,
@@ -295,13 +366,22 @@ export class Breadcrumbs implements Integration {
295
366
function ( this : SentryWrappedXMLHttpRequest , ...args : any [ ] ) : void {
296
367
const xhr = this ; // tslint:disable-line:no-this-assignment
297
368
369
+ if ( xhr . __sentry_own_request__ ) {
370
+ addSentryBreadcrumb ( args [ 0 ] ) ;
371
+ }
372
+
298
373
/** JSDoc */
299
374
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
+ }
301
379
try {
302
380
// touching statusCode in some platforms throws
303
381
// an exception
304
- xhr . __sentry_xhr__ . status_code = xhr . status ;
382
+ if ( xhr . __sentry_xhr__ ) {
383
+ xhr . __sentry_xhr__ . status_code = xhr . status ;
384
+ }
305
385
} catch ( e ) {
306
386
/* do nothing */
307
387
}
@@ -368,6 +448,9 @@ export class Breadcrumbs implements Integration {
368
448
if ( this . config . fetch ) {
369
449
this . instrumentFetch ( { filterUrl } ) ;
370
450
}
451
+ if ( this . config . beacon ) {
452
+ this . instrumentBeacon ( { filterUrl } ) ;
453
+ }
371
454
if ( this . config . history ) {
372
455
this . instrumentHistory ( ) ;
373
456
}
0 commit comments