diff --git a/src/raven.js b/src/raven.js index b246158ada22..1ac443a21e81 100644 --- a/src/raven.js +++ b/src/raven.js @@ -81,7 +81,8 @@ function Raven() { stackTraceLimit: 50, autoBreadcrumbs: true, instrument: true, - sampleRate: 1 + sampleRate: 1, + preferFetch: false }; this._ignoreOnError = 0; this._isRavenInstalled = false; @@ -1232,7 +1233,7 @@ Raven.prototype = { _window, 'fetch', function(origFetch) { - return function(fn, t) { + return function() { // preserve arity // Make a copy of the arguments to prevent deoptimization // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments @@ -1256,6 +1257,11 @@ Raven.prototype = { url = '' + fetchInput; } + // if Sentry key appears in URL, don't capture + if (url.indexOf(self._globalKey) !== -1) { + return origFetch.apply(this, args); + } + if (args[1] && args[1].method) { method = args[1].method; } @@ -1675,29 +1681,17 @@ Raven.prototype = { return true; }, - _setBackoffState: function(request) { + _setBackoffState: function(status, retry) { // If we are already in a backoff state, don't change anything if (this._shouldBackoff()) { return; } - var status = request.status; - // 400 - project_id doesn't exist or some other fatal // 401 - invalid/revoked dsn // 429 - too many requests if (!(status === 400 || status === 401 || status === 429)) return; - var retry; - try { - // If Retry-After is not in Access-Control-Expose-Headers, most - // browsers will throw an exception trying to access it - retry = request.getResponseHeader('Retry-After'); - retry = parseInt(retry, 10) * 1000; // Retry-After is returned in seconds - } catch (e) { - /* eslint no-empty:0 */ - } - this._backoffDuration = retry ? // If Sentry server returned a Retry-After value, use it retry @@ -1867,8 +1861,25 @@ Raven.prototype = { onError: function failure(error) { self._logDebug('error', 'Raven transport failed to send: ', error); - if (error.request) { - self._setBackoffState(error.request); + if (error.xhrRequest || error.fetchResponse) { + var retry; + var status; + if (error.xhrRequest) { + status = error.xhrRequest.status; + try { + // If Retry-After is not in Access-Control-Expose-Headers, most + // browsers will throw an exception trying to access it + retry = error.xhrRequest.getResponseHeader('Retry-After'); + } catch (e) { + /* eslint no-empty:0 */ + } + } else if (error.fetchResponse) { + status = error.fetchResponse.status; + if (error.fetchResponse.headers.has('Retry-After')) { + retry = error.fetchResponse.headers.get('Retry-After'); + } + } + self._setBackoffState(status, retry && parseInt(retry, 10) * 1000); } self._triggerEvent('failure', { @@ -1882,9 +1893,36 @@ Raven.prototype = { }, _makeRequest: function(opts) { + var hasFetch = !!(_window.fetch && _window.Request); + if (this._globalOptions.preferFetch && hasFetch) { + return this._doFetch(opts); + } var request = _window.XMLHttpRequest && new _window.XMLHttpRequest(); - if (!request) return; + if (request) { + return this._doXHR(request, opts); + } else if (hasFetch) { + return this._doFetch(opts); + } + }, + + _doFetch: function(opts) { + _window + .fetch(opts.url + '?' + urlencode(opts.auth), { + method: 'POST', + body: stringify(opts.data) + }) + .then(function(resp) { + if (resp.ok) { + opts.onSuccess && opts.onSuccess(); + } else if (opts.onError) { + var err = new Error('Sentry error code: ' + resp.status); + err.fetchResponse = resp; + opts.onError(err); + } + }, opts.onError); + }, + _doXHR: function(request, opts) { // if browser doesn't support CORS (e.g. IE7), we are out of luck var hasCORS = 'withCredentials' in request || typeof XDomainRequest !== 'undefined'; @@ -1900,7 +1938,7 @@ Raven.prototype = { opts.onSuccess && opts.onSuccess(); } else if (opts.onError) { var err = new Error('Sentry error code: ' + request.status); - err.request = request; + err.xhrRequest = request; opts.onError(err); } }; @@ -1917,7 +1955,7 @@ Raven.prototype = { if (opts.onError) { request.onerror = function() { var err = new Error('Sentry error code: XDomainRequest'); - err.request = request; + err.xhrRequest = request; opts.onError(err); }; } diff --git a/test/raven.test.js b/test/raven.test.js index 486881310d80..db32f24eb734 100644 --- a/test/raven.test.js +++ b/test/raven.test.js @@ -1471,7 +1471,7 @@ describe('globals', function() { Raven._send({message: 'bar'}); var opts = Raven._makeRequest.lastCall.args[0]; var mockError = new Error('429: Too many requests'); - mockError.request = { + mockError.xhrRequest = { status: 429 }; opts.onError(mockError); @@ -1502,7 +1502,7 @@ describe('globals', function() { Raven._send({message: 'bar'}); var opts = Raven._makeRequest.lastCall.args[0]; var mockError = new Error('401: Unauthorized'); - mockError.request = { + mockError.xhrRequest = { status: 401, getResponseHeader: sinon .stub() @@ -1714,7 +1714,7 @@ describe('globals', function() { data: {foo: 'bar'}, options: Raven._globalOptions, onError: function(error) { - assert.equal(error.request.status, 429); + assert.equal(error.xhrRequest.status, 429); done(); } });