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/dist/rollbar.js +146 -55
- package/dist/rollbar.js.map +1 -1
- package/dist/rollbar.min.js +1 -1
- package/dist/rollbar.min.js.map +1 -1
- package/dist/rollbar.named-amd.js +146 -55
- package/dist/rollbar.named-amd.js.map +1 -1
- package/dist/rollbar.named-amd.min.js +1 -1
- package/dist/rollbar.named-amd.min.js.map +1 -1
- package/dist/rollbar.noconflict.umd.js +146 -55
- package/dist/rollbar.noconflict.umd.js.map +1 -1
- package/dist/rollbar.noconflict.umd.min.js +1 -1
- package/dist/rollbar.noconflict.umd.min.js.map +1 -1
- package/dist/rollbar.snippet.js +1 -1
- package/dist/rollbar.umd.js +146 -55
- package/dist/rollbar.umd.js.map +1 -1
- package/dist/rollbar.umd.min.js +1 -1
- package/dist/rollbar.umd.min.js.map +1 -1
- package/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/apiUtility.js +14 -2
- package/src/browser/telemetry.js +4 -0
- package/src/browser/transport/fetch.js +35 -0
- package/src/browser/transport/xhr.js +159 -0
- package/src/browser/transport.js +29 -166
- package/src/defaults.js +1 -1
- package/test/apiUtility.test.js +54 -0
- package/test/browser.rollbar.test.js +15 -20
- package/test/browser.transport.test.js +59 -0
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
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
|
|
package/src/browser/telemetry.js
CHANGED
|
@@ -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;
|
package/src/browser/transport.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
/*global XDomainRequest*/
|
|
2
|
-
|
|
3
1
|
var _ = require('../utility');
|
|
4
|
-
var
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
package/test/apiUtility.test.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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: '
|
|
1558
|
+
body: JSON.stringify({name: 'bar', secret: 'fetch post'})
|
|
1565
1559
|
};
|
|
1566
|
-
var fetchRequest = new Request('https://example.com/
|
|
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(
|
|
1581
|
-
|
|
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
|
-
|
|
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();
|