rollbar 2.25.2 → 2.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -75,7 +75,7 @@ declare namespace Rollbar {
75
75
  hostBlockList?: string[];
76
76
  hostWhiteList?: string[]; // deprecated
77
77
  hostSafeList?: string[];
78
- ignoredMessages?: string[];
78
+ ignoredMessages?: (string | RegExp)[];
79
79
  ignoreDuplicateErrors?: boolean;
80
80
  includeItemsInTelemetry?: boolean;
81
81
  inspectAnonymousErrors?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rollbar",
3
- "version": "2.25.2",
3
+ "version": "2.26.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "http://github.com/rollbar/rollbar.js"
package/src/apiUtility.js CHANGED
@@ -25,6 +25,7 @@ function getTransportFromOptions(options, defaults, url) {
25
25
  var path = defaults.path;
26
26
  var search = defaults.search;
27
27
  var timeout = options.timeout;
28
+ var transport = detectTransport(options)
28
29
 
29
30
  var proxy = options.proxy;
30
31
  if (options.endpoint) {
@@ -42,16 +43,26 @@ function getTransportFromOptions(options, defaults, url) {
42
43
  port: port,
43
44
  path: path,
44
45
  search: search,
45
- proxy: proxy
46
+ proxy: proxy,
47
+ transport: transport
46
48
  };
47
49
  }
48
50
 
51
+ function detectTransport(options) {
52
+ var gWindow = ((typeof window != 'undefined') && window) || ((typeof self != 'undefined') && self);
53
+ var transport = options.defaultTransport || 'xhr';
54
+ if (typeof gWindow.fetch === 'undefined') transport = 'xhr';
55
+ if (typeof gWindow.XMLHttpRequest === 'undefined') transport = 'fetch';
56
+ return transport;
57
+ }
58
+
49
59
  function transportOptions(transport, method) {
50
60
  var protocol = transport.protocol || 'https:';
51
61
  var port = transport.port || (protocol === 'http:' ? 80 : protocol === 'https:' ? 443 : undefined);
52
62
  var hostname = transport.hostname;
53
63
  var path = transport.path;
54
64
  var timeout = transport.timeout;
65
+ var transportAPI = transport.transport;
55
66
  if (transport.search) {
56
67
  path = path + transport.search;
57
68
  }
@@ -67,7 +78,8 @@ function transportOptions(transport, method) {
67
78
  hostname: hostname,
68
79
  path: path,
69
80
  port: port,
70
- method: method
81
+ method: method,
82
+ transport: transportAPI
71
83
  };
72
84
  }
73
85
 
@@ -383,6 +383,9 @@ Instrumenter.prototype.instrumentNetwork = function() {
383
383
  if (self.trackHttpErrors()) {
384
384
  metadata.stack = (new Error()).stack;
385
385
  }
386
+
387
+ // Start our handler before returning the promise. This allows resp.clone()
388
+ // to execute before other handlers touch the response.
386
389
  return orig.apply(this, args).then(function (resp) {
387
390
  metadata.end_time_ms = _.now();
388
391
  metadata.status_code = resp.status;
@@ -395,6 +398,7 @@ Instrumenter.prototype.instrumentNetwork = function() {
395
398
  if (self.autoInstrument.networkResponseBody) {
396
399
  if (typeof resp.text === 'function') { // Response.text() is not implemented on some platforms
397
400
  // The response must be cloned to prevent reading (and locking) the original stream.
401
+ // This must be done before other handlers touch the response.
398
402
  body = resp.clone().text(); //returns a Promise
399
403
  }
400
404
  }
@@ -0,0 +1,35 @@
1
+ var logger = require('../logger');
2
+ var _ = require('../../utility');
3
+
4
+ function makeFetchRequest(accessToken, url, method, data, callback, timeout) {
5
+ var controller;
6
+ var timeoutId;
7
+
8
+ if(_.isFiniteNumber(timeout)) {
9
+ controller = new AbortController();
10
+ timeoutId = setTimeout(() => controller.abort(), timeout);
11
+ }
12
+
13
+ fetch(url, {
14
+ method: method,
15
+ headers: {
16
+ 'Content-Type': 'application/json',
17
+ 'X-Rollbar-Access-Token': accessToken,
18
+ signal: controller && controller.signal
19
+ },
20
+ body: data,
21
+ })
22
+ .then((response) => {
23
+ if (timeoutId) clearTimeout(timeoutId);
24
+ return response.json();
25
+ })
26
+ .then((data) => {
27
+ callback(null, data);
28
+ })
29
+ .catch((error) => {
30
+ logger.error(error.message);
31
+ callback(error);
32
+ });
33
+ }
34
+
35
+ module.exports = makeFetchRequest;
@@ -0,0 +1,159 @@
1
+ /*global XDomainRequest*/
2
+
3
+ var _ = require('../../utility');
4
+ var logger = require('../logger');
5
+
6
+ function makeXhrRequest(accessToken, url, method, data, callback, requestFactory, timeout) {
7
+ var request;
8
+ if (requestFactory) {
9
+ request = requestFactory();
10
+ } else {
11
+ request = _createXMLHTTPObject();
12
+ }
13
+ if (!request) {
14
+ // Give up, no way to send requests
15
+ return callback(new Error('No way to send a request'));
16
+ }
17
+ try {
18
+ try {
19
+ var onreadystatechange = function() {
20
+ try {
21
+ if (onreadystatechange && request.readyState === 4) {
22
+ onreadystatechange = undefined;
23
+
24
+ var parseResponse = _.jsonParse(request.responseText);
25
+ if (_isSuccess(request)) {
26
+ callback(parseResponse.error, parseResponse.value);
27
+ return;
28
+ } else if (_isNormalFailure(request)) {
29
+ if (request.status === 403) {
30
+ // likely caused by using a server access token
31
+ var message = parseResponse.value && parseResponse.value.message;
32
+ logger.error(message);
33
+ }
34
+ // return valid http status codes
35
+ callback(new Error(String(request.status)));
36
+ } else {
37
+ // IE will return a status 12000+ on some sort of connection failure,
38
+ // so we return a blank error
39
+ // http://msdn.microsoft.com/en-us/library/aa383770%28VS.85%29.aspx
40
+ var msg = 'XHR response had no status code (likely connection failure)';
41
+ callback(_newRetriableError(msg));
42
+ }
43
+ }
44
+ } catch (ex) {
45
+ //jquery source mentions firefox may error out while accessing the
46
+ //request members if there is a network error
47
+ //https://github.com/jquery/jquery/blob/a938d7b1282fc0e5c52502c225ae8f0cef219f0a/src/ajax/xhr.js#L111
48
+ var exc;
49
+ if (ex && ex.stack) {
50
+ exc = ex;
51
+ } else {
52
+ exc = new Error(ex);
53
+ }
54
+ callback(exc);
55
+ }
56
+ };
57
+
58
+ request.open(method, url, true);
59
+ if (request.setRequestHeader) {
60
+ request.setRequestHeader('Content-Type', 'application/json');
61
+ request.setRequestHeader('X-Rollbar-Access-Token', accessToken);
62
+ }
63
+
64
+ if(_.isFiniteNumber(timeout)) {
65
+ request.timeout = timeout;
66
+ }
67
+
68
+ request.onreadystatechange = onreadystatechange;
69
+ request.send(data);
70
+ } catch (e1) {
71
+ // Sending using the normal xmlhttprequest object didn't work, try XDomainRequest
72
+ if (typeof XDomainRequest !== 'undefined') {
73
+
74
+ // Assume we are in a really old browser which has a bunch of limitations:
75
+ // http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx
76
+
77
+ // Extreme paranoia: if we have XDomainRequest then we have a window, but just in case
78
+ if (!window || !window.location) {
79
+ return callback(new Error('No window available during request, unknown environment'));
80
+ }
81
+
82
+ // If the current page is http, try and send over http
83
+ if (window.location.href.substring(0, 5) === 'http:' && url.substring(0, 5) === 'https') {
84
+ url = 'http' + url.substring(5);
85
+ }
86
+
87
+ var xdomainrequest = new XDomainRequest();
88
+ xdomainrequest.onprogress = function() {};
89
+ xdomainrequest.ontimeout = function() {
90
+ var msg = 'Request timed out';
91
+ var code = 'ETIMEDOUT';
92
+ callback(_newRetriableError(msg, code));
93
+ };
94
+ xdomainrequest.onerror = function() {
95
+ callback(new Error('Error during request'));
96
+ };
97
+ xdomainrequest.onload = function() {
98
+ var parseResponse = _.jsonParse(xdomainrequest.responseText);
99
+ callback(parseResponse.error, parseResponse.value);
100
+ };
101
+ xdomainrequest.open(method, url, true);
102
+ xdomainrequest.send(data);
103
+ } else {
104
+ callback(new Error('Cannot find a method to transport a request'));
105
+ }
106
+ }
107
+ } catch (e2) {
108
+ callback(e2);
109
+ }
110
+ }
111
+
112
+ function _createXMLHTTPObject() {
113
+ /* global ActiveXObject:false */
114
+
115
+ var factories = [
116
+ function () {
117
+ return new XMLHttpRequest();
118
+ },
119
+ function () {
120
+ return new ActiveXObject('Msxml2.XMLHTTP');
121
+ },
122
+ function () {
123
+ return new ActiveXObject('Msxml3.XMLHTTP');
124
+ },
125
+ function () {
126
+ return new ActiveXObject('Microsoft.XMLHTTP');
127
+ }
128
+ ];
129
+ var xmlhttp;
130
+ var i;
131
+ var numFactories = factories.length;
132
+ for (i = 0; i < numFactories; i++) {
133
+ /* eslint-disable no-empty */
134
+ try {
135
+ xmlhttp = factories[i]();
136
+ break;
137
+ } catch (e) {
138
+ // pass
139
+ }
140
+ /* eslint-enable no-empty */
141
+ }
142
+ return xmlhttp;
143
+ }
144
+
145
+ function _isSuccess(r) {
146
+ return r && r.status && r.status === 200;
147
+ }
148
+
149
+ function _isNormalFailure(r) {
150
+ return r && _.isType(r.status, 'number') && r.status >= 400 && r.status < 600;
151
+ }
152
+
153
+ function _newRetriableError(message, code) {
154
+ var err = new Error(message);
155
+ err.code = code || 'ENOTFOUND';
156
+ return err;
157
+ }
158
+
159
+ module.exports = makeXhrRequest;
@@ -1,7 +1,6 @@
1
- /*global XDomainRequest*/
2
-
3
1
  var _ = require('../utility');
4
- var logger = require('./logger');
2
+ var makeFetchRequest = require('./transport/fetch');
3
+ var makeXhrRequest = require('./transport/xhr');
5
4
 
6
5
  /*
7
6
  * accessToken may be embedded in payload but that should not
@@ -13,6 +12,7 @@ var logger = require('./logger');
13
12
  * path
14
13
  * port
15
14
  * method
15
+ * transport ('xhr' | 'fetch')
16
16
  * }
17
17
  *
18
18
  * params is an object containing key/value pairs. These
@@ -32,7 +32,9 @@ Transport.prototype.get = function(accessToken, options, params, callback, reque
32
32
 
33
33
  var method = 'GET';
34
34
  var url = _.formatUrl(options);
35
- _makeZoneRequest(accessToken, url, method, null, callback, requestFactory, options.timeout);
35
+ this._makeZoneRequest(
36
+ accessToken, url, method, null, callback, requestFactory, options.timeout, options.transport
37
+ );
36
38
  }
37
39
 
38
40
  Transport.prototype.post = function(accessToken, options, payload, callback, requestFactory) {
@@ -57,7 +59,9 @@ Transport.prototype.post = function(accessToken, options, payload, callback, req
57
59
  var writeData = stringifyResult.value;
58
60
  var method = 'POST';
59
61
  var url = _.formatUrl(options);
60
- _makeZoneRequest(accessToken, url, method, writeData, callback, requestFactory, options.timeout);
62
+ this._makeZoneRequest(
63
+ accessToken, url, method, writeData, callback, requestFactory, options.timeout, options.transport
64
+ );
61
65
  }
62
66
 
63
67
  Transport.prototype.postJsonPayload = function (accessToken, options, jsonPayload, callback, requestFactory) {
@@ -67,7 +71,9 @@ Transport.prototype.postJsonPayload = function (accessToken, options, jsonPayloa
67
71
 
68
72
  var method = 'POST';
69
73
  var url = _.formatUrl(options);
70
- _makeZoneRequest(accessToken, url, method, jsonPayload, callback, requestFactory, options.timeout);
74
+ this._makeZoneRequest(
75
+ accessToken, url, method, jsonPayload, callback, requestFactory, options.timeout, options.transport
76
+ );
71
77
  }
72
78
 
73
79
 
@@ -75,7 +81,7 @@ Transport.prototype.postJsonPayload = function (accessToken, options, jsonPayloa
75
81
  // so Angular change detection isn't triggered on each API call.
76
82
  // This is the equivalent of runOutsideAngular().
77
83
  //
78
- function _makeZoneRequest() {
84
+ Transport.prototype._makeZoneRequest = function () {
79
85
  var gWindow = ((typeof window != 'undefined') && window) || ((typeof self != 'undefined') && self);
80
86
  var currentZone = gWindow && gWindow.Zone && gWindow.Zone.current;
81
87
  var args = Array.prototype.slice.call(arguments);
@@ -83,10 +89,24 @@ function _makeZoneRequest() {
83
89
  if (currentZone && currentZone._name === 'angular') {
84
90
  var rootZone = currentZone._parent;
85
91
  rootZone.run(function () {
86
- _makeRequest.apply(undefined, args);
92
+ this._makeRequest.apply(undefined, args);
87
93
  });
88
94
  } else {
89
- _makeRequest.apply(undefined, args);
95
+ this._makeRequest.apply(undefined, args);
96
+ }
97
+ }
98
+
99
+ Transport.prototype._makeRequest = function (
100
+ accessToken, url, method, data, callback, requestFactory, timeout, transport
101
+ ) {
102
+ if (typeof RollbarProxy !== 'undefined') {
103
+ return _proxyRequest(data, callback);
104
+ }
105
+
106
+ if (transport === 'fetch') {
107
+ makeFetchRequest(accessToken, url, method, data, callback, timeout)
108
+ } else {
109
+ makeXhrRequest(accessToken, url, method, data, callback, requestFactory, timeout)
90
110
  }
91
111
  }
92
112
 
@@ -102,161 +122,4 @@ function _proxyRequest(json, callback) {
102
122
  );
103
123
  }
104
124
 
105
- function _makeRequest(accessToken, url, method, data, callback, requestFactory, timeout) {
106
- if (typeof RollbarProxy !== 'undefined') {
107
- return _proxyRequest(data, callback);
108
- }
109
-
110
- var request;
111
- if (requestFactory) {
112
- request = requestFactory();
113
- } else {
114
- request = _createXMLHTTPObject();
115
- }
116
- if (!request) {
117
- // Give up, no way to send requests
118
- return callback(new Error('No way to send a request'));
119
- }
120
- try {
121
- try {
122
- var onreadystatechange = function() {
123
- try {
124
- if (onreadystatechange && request.readyState === 4) {
125
- onreadystatechange = undefined;
126
-
127
- var parseResponse = _.jsonParse(request.responseText);
128
- if (_isSuccess(request)) {
129
- callback(parseResponse.error, parseResponse.value);
130
- return;
131
- } else if (_isNormalFailure(request)) {
132
- if (request.status === 403) {
133
- // likely caused by using a server access token
134
- var message = parseResponse.value && parseResponse.value.message;
135
- logger.error(message);
136
- }
137
- // return valid http status codes
138
- callback(new Error(String(request.status)));
139
- } else {
140
- // IE will return a status 12000+ on some sort of connection failure,
141
- // so we return a blank error
142
- // http://msdn.microsoft.com/en-us/library/aa383770%28VS.85%29.aspx
143
- var msg = 'XHR response had no status code (likely connection failure)';
144
- callback(_newRetriableError(msg));
145
- }
146
- }
147
- } catch (ex) {
148
- //jquery source mentions firefox may error out while accessing the
149
- //request members if there is a network error
150
- //https://github.com/jquery/jquery/blob/a938d7b1282fc0e5c52502c225ae8f0cef219f0a/src/ajax/xhr.js#L111
151
- var exc;
152
- if (ex && ex.stack) {
153
- exc = ex;
154
- } else {
155
- exc = new Error(ex);
156
- }
157
- callback(exc);
158
- }
159
- };
160
-
161
- request.open(method, url, true);
162
- if (request.setRequestHeader) {
163
- request.setRequestHeader('Content-Type', 'application/json');
164
- request.setRequestHeader('X-Rollbar-Access-Token', accessToken);
165
- }
166
-
167
- if(_.isFiniteNumber(timeout)) {
168
- request.timeout = timeout;
169
- }
170
-
171
- request.onreadystatechange = onreadystatechange;
172
- request.send(data);
173
- } catch (e1) {
174
- // Sending using the normal xmlhttprequest object didn't work, try XDomainRequest
175
- if (typeof XDomainRequest !== 'undefined') {
176
-
177
- // Assume we are in a really old browser which has a bunch of limitations:
178
- // http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx
179
-
180
- // Extreme paranoia: if we have XDomainRequest then we have a window, but just in case
181
- if (!window || !window.location) {
182
- return callback(new Error('No window available during request, unknown environment'));
183
- }
184
-
185
- // If the current page is http, try and send over http
186
- if (window.location.href.substring(0, 5) === 'http:' && url.substring(0, 5) === 'https') {
187
- url = 'http' + url.substring(5);
188
- }
189
-
190
- var xdomainrequest = new XDomainRequest();
191
- xdomainrequest.onprogress = function() {};
192
- xdomainrequest.ontimeout = function() {
193
- var msg = 'Request timed out';
194
- var code = 'ETIMEDOUT';
195
- callback(_newRetriableError(msg, code));
196
- };
197
- xdomainrequest.onerror = function() {
198
- callback(new Error('Error during request'));
199
- };
200
- xdomainrequest.onload = function() {
201
- var parseResponse = _.jsonParse(xdomainrequest.responseText);
202
- callback(parseResponse.error, parseResponse.value);
203
- };
204
- xdomainrequest.open(method, url, true);
205
- xdomainrequest.send(data);
206
- } else {
207
- callback(new Error('Cannot find a method to transport a request'));
208
- }
209
- }
210
- } catch (e2) {
211
- callback(e2);
212
- }
213
- }
214
-
215
- function _createXMLHTTPObject() {
216
- /* global ActiveXObject:false */
217
-
218
- var factories = [
219
- function () {
220
- return new XMLHttpRequest();
221
- },
222
- function () {
223
- return new ActiveXObject('Msxml2.XMLHTTP');
224
- },
225
- function () {
226
- return new ActiveXObject('Msxml3.XMLHTTP');
227
- },
228
- function () {
229
- return new ActiveXObject('Microsoft.XMLHTTP');
230
- }
231
- ];
232
- var xmlhttp;
233
- var i;
234
- var numFactories = factories.length;
235
- for (i = 0; i < numFactories; i++) {
236
- /* eslint-disable no-empty */
237
- try {
238
- xmlhttp = factories[i]();
239
- break;
240
- } catch (e) {
241
- // pass
242
- }
243
- /* eslint-enable no-empty */
244
- }
245
- return xmlhttp;
246
- }
247
-
248
- function _isSuccess(r) {
249
- return r && r.status && r.status === 200;
250
- }
251
-
252
- function _isNormalFailure(r) {
253
- return r && _.isType(r.status, 'number') && r.status >= 400 && r.status < 600;
254
- }
255
-
256
- function _newRetriableError(message, code) {
257
- var err = new Error(message);
258
- err.code = code || 'ENOTFOUND';
259
- return err;
260
- }
261
-
262
125
  module.exports = Transport;
package/src/defaults.js CHANGED
@@ -1,5 +1,5 @@
1
1
  module.exports = {
2
- version: '2.25.2',
2
+ version: '2.26.0',
3
3
  endpoint: 'api.rollbar.com/api/1/item/',
4
4
  logLevel: 'debug',
5
5
  reportLevel: 'debug',
@@ -105,6 +105,60 @@ describe('getTransportFromOptions', function() {
105
105
  expect(t.proxy).to.eql(options.proxy);
106
106
  expect(t.timeout).to.eql(undefined);
107
107
  });
108
+ describe('getTransportFromOptions', function() {
109
+ var defaults = {
110
+ hostname: 'api.com',
111
+ protocol: 'https:',
112
+ path: '/api/1',
113
+ search: '?abc=456',
114
+ };
115
+ var url = {
116
+ parse: function(_) {
117
+ return {
118
+ hostname: 'whatever.com',
119
+ protocol: 'http:',
120
+ pathname: '/api/42'
121
+ };
122
+ }
123
+ };
124
+ it('should use xhr by default', function(done) {
125
+ var options = {};
126
+ var t = u.getTransportFromOptions(options, defaults, url);
127
+ expect(t.transport).to.eql('xhr');
128
+ done();
129
+ });
130
+ it('should use fetch when requested', function(done) {
131
+ var options = {defaultTransport: 'fetch'};
132
+ var t = u.getTransportFromOptions(options, defaults, url);
133
+ expect(t.transport).to.eql('fetch');
134
+ done();
135
+ });
136
+ it('should use xhr when requested', function(done) {
137
+ var options = {defaultTransport: 'xhr'};
138
+ var t = u.getTransportFromOptions(options, defaults, url);
139
+ expect(t.transport).to.eql('xhr');
140
+ done();
141
+ });
142
+ it('should use xhr when fetch is unavailable', function(done) {
143
+ var options = {defaultTransport: 'fetch'};
144
+ var oldFetch = window.fetch;
145
+ self.fetch = undefined;
146
+ var t = u.getTransportFromOptions(options, defaults, url);
147
+ expect(t.transport).to.eql('xhr');
148
+ self.fetch = oldFetch;
149
+ done();
150
+ });
151
+ it('should use fetch when xhr is unavailable', function(done) {
152
+ var options = {defaultTransport: 'xhr'};
153
+ var oldXhr = window.XMLHttpRequest;
154
+ self.XMLHttpRequest = undefined;
155
+ var t = u.getTransportFromOptions(options, defaults, url);
156
+ expect(t.transport).to.eql('fetch');
157
+ self.XMLHttpRequest = oldXhr;
158
+ done();
159
+ });
160
+ });
161
+
108
162
  });
109
163
 
110
164
  describe('transportOptions', function() {
@@ -1529,15 +1529,9 @@ describe('options.autoInstrument', function() {
1529
1529
 
1530
1530
  window.fetchStub = sinon.stub(window, 'fetch');
1531
1531
 
1532
- var readableStream = new ReadableStream({
1533
- start(controller) {
1534
- controller.enqueue(JSON.stringify({name: 'foo', password: '123456'}));
1535
- controller.close();
1536
- }
1537
- });
1538
-
1532
+ var responseBody = JSON.stringify({name: 'foo', password: '123456'});
1539
1533
  window.fetch.returns(Promise.resolve(new Response(
1540
- readableStream,
1534
+ responseBody,
1541
1535
  { status: 200, statusText: 'OK', headers: { 'content-type': 'application/json', 'password': '123456' }}
1542
1536
  )));
1543
1537
 
@@ -1561,11 +1555,18 @@ describe('options.autoInstrument', function() {
1561
1555
  const fetchInit = {
1562
1556
  method: 'POST',
1563
1557
  headers: fetchHeaders,
1564
- body: JSON.stringify({name: 'bar', secret: 'xhr post'})
1558
+ body: JSON.stringify({name: 'bar', secret: 'fetch post'})
1565
1559
  };
1566
- var fetchRequest = new Request('https://example.com/xhr-test');
1560
+ var fetchRequest = new Request('https://example.com/fetch-test');
1567
1561
  window.fetch(fetchRequest, fetchInit)
1568
1562
  .then(function(response) {
1563
+ // Assert that the original stream reader hasn't been read.
1564
+ expect(response.bodyUsed).to.eql(false);
1565
+ return response.text()
1566
+ })
1567
+ .then(function(text) {
1568
+ expect(text).to.eql(responseBody);
1569
+
1569
1570
  try {
1570
1571
  rollbar.log('test'); // generate a payload to inspect
1571
1572
  } catch (e) {
@@ -1577,8 +1578,9 @@ describe('options.autoInstrument', function() {
1577
1578
  try {
1578
1579
  server.respond();
1579
1580
 
1580
- expect(server.requests.length).to.eql(2);
1581
- var body = JSON.parse(server.requests[1].requestBody);
1581
+ expect(window.fetchStub.called).to.be.ok();
1582
+ expect(server.requests.length).to.eql(1);
1583
+ var body = JSON.parse(server.requests[0].requestBody);
1582
1584
 
1583
1585
  // Verify request capture and scrubbing
1584
1586
  expect(body.data.body.telemetry[0].body.request).to.eql('{"name":"bar","secret":"********"}');
@@ -1586,19 +1588,12 @@ describe('options.autoInstrument', function() {
1586
1588
  // Verify request headers capture and case-insensitive scrubbing
1587
1589
  expect(body.data.body.telemetry[0].body.request_headers).to.eql({'content-type': 'application/json', secret: '********'});
1588
1590
 
1589
- // When using the Sinon test stub, the response body is populated in Headless Chrome 73,
1590
- // but not in 77. When using the Fetch API normally, it is populated in all tested Chrome versions.
1591
- // Disable here due to the Sinon limitation.
1592
- //
1593
1591
  // Verify response capture and scrubbing
1594
- // expect(body.data.body.telemetry[0].body.response.body).to.eql('{"name":"foo","password":"********"}');
1592
+ expect(body.data.body.telemetry[0].body.response.body).to.eql('{"name":"foo","password":"********"}');
1595
1593
 
1596
1594
  // Verify response headers capture and case-insensitive scrubbing
1597
1595
  expect(body.data.body.telemetry[0].body.response.headers).to.eql({'content-type': 'application/json', password: '********'});
1598
1596
 
1599
- // Assert that the original stream reader hasn't been read.
1600
- expect(response.bodyUsed).to.eql(false);
1601
-
1602
1597
  rollbar.configure({ autoInstrument: false });
1603
1598
  window.fetch.restore();
1604
1599
  done();