rollbar 2.25.1 → 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.1",
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
 
@@ -1,4 +1,5 @@
1
1
  var _ = require('../utility');
2
+ var headers = require('../utility/headers');
2
3
  var scrub = require('../scrub');
3
4
  var urlparser = require('./url');
4
5
  var domUtil = require('./domUtility');
@@ -362,7 +363,7 @@ Instrumenter.prototype.instrumentNetwork = function() {
362
363
  if (args[1] && args[1].headers) {
363
364
  // Argument may be a Headers object, or plain object. Ensure here that
364
365
  // we are working with a Headers object with case-insensitive keys.
365
- var reqHeaders = new Headers(args[1].headers);
366
+ var reqHeaders = headers(args[1].headers);
366
367
 
367
368
  metadata.request_content_type = reqHeaders.get('Content-Type');
368
369
 
@@ -382,6 +383,9 @@ Instrumenter.prototype.instrumentNetwork = function() {
382
383
  if (self.trackHttpErrors()) {
383
384
  metadata.stack = (new Error()).stack;
384
385
  }
386
+
387
+ // Start our handler before returning the promise. This allows resp.clone()
388
+ // to execute before other handlers touch the response.
385
389
  return orig.apply(this, args).then(function (resp) {
386
390
  metadata.end_time_ms = _.now();
387
391
  metadata.status_code = resp.status;
@@ -394,6 +398,7 @@ Instrumenter.prototype.instrumentNetwork = function() {
394
398
  if (self.autoInstrument.networkResponseBody) {
395
399
  if (typeof resp.text === 'function') { // Response.text() is not implemented on some platforms
396
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.
397
402
  body = resp.clone().text(); //returns a Promise
398
403
  }
399
404
  }
@@ -79,20 +79,25 @@ function addBaseInfo(item, options, callback) {
79
79
 
80
80
  function addRequestInfo(window) {
81
81
  return function(item, options, callback) {
82
- if (!window || !window.location) {
83
- return callback(null, item);
82
+ var requestInfo = {};
83
+
84
+ if (window && window.location) {
85
+ requestInfo.url = window.location.href;
86
+ requestInfo.query_string = window.location.search;
84
87
  }
88
+
85
89
  var remoteString = '$remote_ip';
86
90
  if (!options.captureIp) {
87
91
  remoteString = null;
88
92
  } else if (options.captureIp !== true) {
89
93
  remoteString += '_anonymize';
90
94
  }
91
- _.set(item, 'data.request', {
92
- url: window.location.href,
93
- query_string: window.location.search,
94
- user_ip: remoteString
95
- });
95
+ if (remoteString) requestInfo.user_ip = remoteString;
96
+
97
+ if (Object.keys(requestInfo).length > 0) {
98
+ _.set(item, 'data.request', requestInfo);
99
+ }
100
+
96
101
  callback(null, item);
97
102
  };
98
103
  }
@@ -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.1',
2
+ version: '2.26.0',
3
3
  endpoint: 'api.rollbar.com/api/1/item/',
4
4
  logLevel: 'debug',
5
5
  reportLevel: 'debug',
@@ -0,0 +1,94 @@
1
+ /*
2
+ * headers - Detect when fetch Headers are undefined and use a partial polyfill.
3
+ *
4
+ * A full polyfill is not used in order to keep package size as small as possible.
5
+ * Since this is only used internally and is not added to the window object,
6
+ * the full interface doesn't need to be supported.
7
+ *
8
+ * This implementation is modified from whatwg-fetch:
9
+ * https://github.com/github/fetch
10
+ */
11
+ function headers(headers) {
12
+ if (typeof Headers === 'undefined') {
13
+ return new FetchHeaders(headers);
14
+ }
15
+
16
+ return new Headers(headers);
17
+ }
18
+
19
+ function normalizeName(name) {
20
+ if (typeof name !== 'string') {
21
+ name = String(name)
22
+ }
23
+ return name.toLowerCase()
24
+ }
25
+
26
+ function normalizeValue(value) {
27
+ if (typeof value !== 'string') {
28
+ value = String(value)
29
+ }
30
+ return value
31
+ }
32
+
33
+ function iteratorFor(items) {
34
+ var iterator = {
35
+ next: function() {
36
+ var value = items.shift()
37
+ return {done: value === undefined, value: value}
38
+ }
39
+ }
40
+
41
+ return iterator
42
+ }
43
+
44
+ function FetchHeaders(headers) {
45
+ this.map = {}
46
+
47
+ if (headers instanceof FetchHeaders) {
48
+ headers.forEach(function(value, name) {
49
+ this.append(name, value)
50
+ }, this)
51
+ } else if (Array.isArray(headers)) {
52
+ headers.forEach(function(header) {
53
+ this.append(header[0], header[1])
54
+ }, this)
55
+ } else if (headers) {
56
+ Object.getOwnPropertyNames(headers).forEach(function(name) {
57
+ this.append(name, headers[name])
58
+ }, this)
59
+ }
60
+ }
61
+
62
+ FetchHeaders.prototype.append = function(name, value) {
63
+ name = normalizeName(name)
64
+ value = normalizeValue(value)
65
+ var oldValue = this.map[name]
66
+ this.map[name] = oldValue ? oldValue + ', ' + value : value
67
+ }
68
+
69
+ FetchHeaders.prototype.get = function(name) {
70
+ name = normalizeName(name)
71
+ return this.has(name) ? this.map[name] : null
72
+ }
73
+
74
+ FetchHeaders.prototype.has = function(name) {
75
+ return this.map.hasOwnProperty(normalizeName(name))
76
+ }
77
+
78
+ FetchHeaders.prototype.forEach = function(callback, thisArg) {
79
+ for (var name in this.map) {
80
+ if (this.map.hasOwnProperty(name)) {
81
+ callback.call(thisArg, this.map[name], name, this)
82
+ }
83
+ }
84
+ }
85
+
86
+ FetchHeaders.prototype.entries = function() {
87
+ var items = []
88
+ this.forEach(function(value, name) {
89
+ items.push([name, value])
90
+ })
91
+ return iteratorFor(items)
92
+ }
93
+
94
+ module.exports = headers;