11const stringify = require ( "./vendor/json-stringify-safe/stringify" ) ;
2+ const pako = require ( 'pako' ) ;
3+
4+ // This is to be defensive in environments where window does not exist (see https://github.com/getsentry/raven-js/pull/785)
5+ const _window =
6+ typeof window !== 'undefined'
7+ ? window
8+ : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : { } ;
9+
10+ /**
11+ * hasKey, a better form of hasOwnProperty
12+ * Example: hasKey(MainHostObject, property) === true/false
13+ *
14+ * @param {Object } host object to check property
15+ * @param {string } key to check
16+ */
17+ function hasKey ( object , key ) {
18+ return Object . prototype . hasOwnProperty . call ( object , key ) ;
19+ }
20+
21+ function isUndefined ( what ) {
22+ return what === void 0 ;
23+ }
24+
25+ function each ( obj , callback ) {
26+ var i , j ;
27+
28+ if ( isUndefined ( obj . length ) ) {
29+ for ( i in obj ) {
30+ if ( hasKey ( obj , i ) ) {
31+ callback . call ( null , i , obj [ i ] ) ;
32+ }
33+ }
34+ } else {
35+ j = obj . length ;
36+ if ( j ) {
37+ for ( i = 0 ; i < j ; i ++ ) {
38+ callback . call ( null , i , obj [ i ] ) ;
39+ }
40+ }
41+ }
42+ }
43+
44+ function supportsFetch ( ) {
45+ if ( ! ( 'fetch' in _window ) ) return false ;
46+
47+ try {
48+ new Headers ( ) ; // eslint-disable-line no-new
49+ new Request ( '' ) ; // eslint-disable-line no-new
50+ new Response ( ) ; // eslint-disable-line no-new
51+ return true ;
52+ } catch ( e ) {
53+ return false ;
54+ }
55+ }
56+
57+ function urlencode ( o ) {
58+ var pairs = [ ] ;
59+ each ( o , function ( key , value ) {
60+ pairs . push ( encodeURIComponent ( key ) + '=' + encodeURIComponent ( value ) ) ;
61+ } ) ;
62+ return pairs . join ( '&' ) ;
63+ }
64+
65+ function objectMerge ( obj1 , obj2 ) {
66+ if ( ! obj2 ) {
67+ return obj1 ;
68+ }
69+ each ( obj2 , function ( key , value ) {
70+ obj1 [ key ] = value ;
71+ } ) ;
72+ return obj1 ;
73+ }
74+
75+
76+ // Unfortunately, this doesn't work at the moment, because Sentry doesn't allow us to
77+ // send a Content-Encoding header. The CORS preflight request returns:
78+ //
79+ // Access-Control-Allow-Headers: X-Sentry-Auth, X-Requested-With,
80+ // Origin, Accept, Content-Type, Authentication
81+ //
82+ // (Content-Encoding is missing.)
83+ //
84+ // Also it might be dangerous for them to support gzip/deflate, because people could
85+ // send gzip bombs (a tiny amount of compressed data that expands to gigabytes on disk)
86+ // But maybe there's a decompression library that can be configured to abort after a max size.
87+
88+ const makeRequestWithZlib = function ( opts , shouldSendRequest ) {
89+ // Auth is intentionally sent as part of query string (NOT as custom HTTP header) to avoid preflight CORS requests
90+ var url = opts . url + '?' + urlencode ( opts . auth ) ;
91+
92+ var evaluatedHeaders = null ;
93+ var evaluatedFetchParameters = { } ;
94+
95+ if ( opts . options . headers ) {
96+ evaluatedHeaders = this . _evaluateHash ( opts . options . headers ) ;
97+ }
98+
99+ if ( opts . options . fetchParameters ) {
100+ evaluatedFetchParameters = this . _evaluateHash ( opts . options . fetchParameters ) ;
101+ }
102+
103+ const requestJSON = stringify ( opts . data ) ;
104+ const requestDeflate = pako . deflate ( requestJSON , { to : 'string' } ) ;
105+
106+ if ( shouldSendRequest && ! shouldSendRequest ( requestDeflate ) ) {
107+ return ;
108+ }
109+
110+ if ( supportsFetch ( ) ) {
111+ var defaultFetchOptions = objectMerge ( { } , this . _fetchDefaults ) ;
112+ var fetchOptions = objectMerge ( defaultFetchOptions , evaluatedFetchParameters ) ;
113+
114+ if ( evaluatedHeaders ) {
115+ fetchOptions . headers = evaluatedHeaders ;
116+ }
117+
118+ evaluatedFetchParameters . body = requestDeflate
119+
120+ fetchOptions . headers = Object . assign ( fetchOptions . headers || { } , {
121+ 'Content-Type' : 'application/json; charset=utf-8' ,
122+ 'Content-Encoding' : 'deflate' ,
123+ } )
124+
125+ return _window
126+ . fetch ( url , fetchOptions )
127+ . then ( function ( response ) {
128+ if ( response . ok ) {
129+ opts . onSuccess && opts . onSuccess ( ) ;
130+ } else {
131+ var error = new Error ( 'Sentry error code: ' + response . status ) ;
132+ // It's called request only to keep compatibility with XHR interface
133+ // and not add more redundant checks in setBackoffState method
134+ error . request = response ;
135+ opts . onError && opts . onError ( error ) ;
136+ }
137+ } )
138+ [ 'catch' ] ( function ( ) {
139+ opts . onError &&
140+ opts . onError ( new Error ( 'Sentry error code: network unavailable' ) ) ;
141+ } ) ;
142+ }
143+
144+ var request = _window . XMLHttpRequest && new _window . XMLHttpRequest ( ) ;
145+ if ( ! request ) return ;
146+
147+ // if browser doesn't support CORS (e.g. IE7), we are out of luck
148+ var hasCORS = 'withCredentials' in request || typeof XDomainRequest !== 'undefined' ;
149+
150+ if ( ! hasCORS ) return ;
151+
152+ if ( 'withCredentials' in request ) {
153+ request . onreadystatechange = function ( ) {
154+ if ( request . readyState !== 4 ) {
155+ return ;
156+ } else if ( request . status === 200 ) {
157+ opts . onSuccess && opts . onSuccess ( ) ;
158+ } else if ( opts . onError ) {
159+ var err = new Error ( 'Sentry error code: ' + request . status ) ;
160+ err . request = request ;
161+ opts . onError ( err ) ;
162+ }
163+ } ;
164+ } else {
165+ request = new XDomainRequest ( ) ;
166+ // xdomainrequest cannot go http -> https (or vice versa),
167+ // so always use protocol relative
168+ url = url . replace ( / ^ h t t p s ? : / , '' ) ;
169+
170+ // onreadystatechange not supported by XDomainRequest
171+ if ( opts . onSuccess ) {
172+ request . onload = opts . onSuccess ;
173+ }
174+ if ( opts . onError ) {
175+ request . onerror = function ( ) {
176+ var err = new Error ( 'Sentry error code: XDomainRequest' ) ;
177+ err . request = request ;
178+ opts . onError ( err ) ;
179+ } ;
180+ }
181+ }
182+
183+ request . open ( 'POST' , url ) ;
184+
185+ if ( evaluatedHeaders ) {
186+ each ( evaluatedHeaders , function ( key , value ) {
187+ request . setRequestHeader ( key , value ) ;
188+ } ) ;
189+ }
190+
191+ request . setRequestHeader ( 'Content-Type' , 'application/json; charset=utf-8' ) ;
192+ request . setRequestHeader ( 'Content-Encoding' , 'deflate' ) ;
193+
194+ request . send ( requestDeflate ) ;
195+ }
196+
2197
3198const identity = x => x ;
4199const getUndefined = ( ) => { } ;
@@ -57,18 +252,6 @@ function createRavenMiddleware(Raven, options = {}) {
57252 const originalTransport = Raven . _globalOptions . transport ;
58253 Raven . setTransport ( opts => {
59254 Raven . setTransport ( originalTransport ) ;
60- const requestBody = stringify ( opts . data ) ;
61- if ( requestBody . length > 200000 ) {
62- // We know the request is too large, so don't try sending it to Sentry.
63- // Retry the capture function, and don't include the state this time.
64- const errorMessage =
65- "Could not send state because request would be larger than 200KB. " +
66- `(Was: ${ requestBody . length } B)` ;
67- retryCaptureWithoutReduxState ( errorMessage , ( ) => {
68- originalFn . apply ( Raven , captureArguments ) ;
69- } ) ;
70- return ;
71- }
72255 opts . onError = error => {
73256 if ( error . request && error . request . status === 413 ) {
74257 const errorMessage =
@@ -78,7 +261,21 @@ function createRavenMiddleware(Raven, options = {}) {
78261 } ) ;
79262 }
80263 } ;
81- ( originalTransport || Raven . _makeRequest ) . call ( Raven , opts ) ;
264+
265+ makeRequestWithZlib ( opts , ( requestBody ) => {
266+ if ( requestBody . length > 200000 ) {
267+ // We know the request is too large, so don't try sending it to Sentry.
268+ // Retry the capture function, and don't include the state this time.
269+ const errorMessage =
270+ "Could not send state because request would be larger than 200KB. " +
271+ `(Was: ${ requestBody . length } B)` ;
272+ retryCaptureWithoutReduxState ( errorMessage , ( ) => {
273+ originalFn . apply ( Raven , captureArguments ) ;
274+ } ) ;
275+ return false ;
276+ }
277+ return true ;
278+ } ) ;
82279 } ) ;
83280 originalFn . apply ( Raven , captureArguments ) ;
84281 } ;
@@ -91,6 +288,9 @@ function createRavenMiddleware(Raven, options = {}) {
91288 Raven . captureMessage
92289 ) ;
93290
291+ // Set the default transport to use zlib compression
292+ Raven . setTransport ( makeRequestWithZlib . bind ( Raven ) ) ;
293+
94294 return next => action => {
95295 // Log the action taken to Raven so that we have narrative context in our
96296 // error report.
0 commit comments