Skip to content

Commit 396775d

Browse files
committed
Use XHR + POST for request if CORS supported
1 parent 18fbd68 commit 396775d

File tree

2 files changed

+115
-7
lines changed

2 files changed

+115
-7
lines changed

src/raven.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -827,7 +827,7 @@ function send(data) {
827827
});
828828
}
829829

830-
function makeRequest(opts) {
830+
function makeImageRequest(opts) {
831831
// Tack on sentry_data to auth options, which get urlencoded
832832
opts.auth.sentry_data = JSON.stringify(opts.data);
833833

@@ -843,6 +843,38 @@ function makeRequest(opts) {
843843
img.src = src;
844844
}
845845

846+
function makeXhrRequest(opts) {
847+
var request = new XMLHttpRequest();
848+
request.onreadystatechange = function (e) {
849+
if (request.readyState !== 4) {
850+
return;
851+
}
852+
853+
if (request.status === 200) {
854+
if (opts.onSuccess) {
855+
opts.onSuccess();
856+
}
857+
} else {
858+
if (opts.onError) {
859+
opts.onError();
860+
}
861+
}
862+
};
863+
864+
// NOTE: auth is intentionally sent as part of query string (NOT as custom
865+
// HTTP header) so as to avoid preflight CORS requests
866+
request.open('POST', opts.url + '?' + urlencode(opts.auth));
867+
request.send(JSON.stringify(opts.data));
868+
}
869+
870+
function makeRequest(opts) {
871+
var hasCORS =
872+
'withCredentials' in new XMLHttpRequest() ||
873+
typeof XDomainRequest !== 'undefined';
874+
875+
return (hasCORS ? makeXhrRequest : makeImageRequest)(opts);
876+
}
877+
846878
// Note: this is shitty, but I can't figure out how to get
847879
// sinon to stub document.createElement without breaking everything
848880
// so this wrapper is just so I can stub it for tests.

test/raven.test.js

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1267,6 +1267,82 @@ describe('globals', function() {
12671267
});
12681268

12691269
describe('makeRequest', function() {
1270+
beforeEach(function() {
1271+
// use fake xml http request so we can muck w/ its prototype
1272+
this.xhr = sinon.useFakeXMLHttpRequest();
1273+
this.sinon.stub(window, 'makeImageRequest');
1274+
this.sinon.stub(window, 'makeXhrRequest');
1275+
});
1276+
1277+
afterEach(function() {
1278+
this.xhr.restore();
1279+
});
1280+
1281+
it('should call makeXhrRequest if CORS is supported', function () {
1282+
XMLHttpRequest.prototype.withCredentials = true;
1283+
1284+
makeRequest({
1285+
url: 'http://localhost/',
1286+
auth: {a: '1', b: '2'},
1287+
data: {foo: 'bar'},
1288+
options: globalOptions
1289+
});
1290+
1291+
assert.isTrue(makeImageRequest.notCalled);
1292+
assert.isTrue(makeXhrRequest.calledOnce);
1293+
});
1294+
1295+
it('should call makeImageRequest if CORS is NOT supported', function () {
1296+
delete XMLHttpRequest.prototype.withCredentials;
1297+
1298+
var oldXDR = window.XDomainRequest;
1299+
window.XDomainRequest = undefined;
1300+
1301+
makeRequest({
1302+
url: 'http://localhost/',
1303+
auth: {a: '1', b: '2'},
1304+
data: {foo: 'bar'},
1305+
options: globalOptions
1306+
});
1307+
1308+
assert.isTrue(makeImageRequest.calledOnce);
1309+
assert.isTrue(makeXhrRequest.notCalled);
1310+
1311+
window.XDomainRequest = oldXDR;
1312+
});
1313+
});
1314+
1315+
describe('makeXhrRequest', function() {
1316+
beforeEach(function() {
1317+
// NOTE: can't seem to call useFakeXMLHttpRequest via sandbox; must
1318+
// restore manually
1319+
this.xhr = sinon.useFakeXMLHttpRequest();
1320+
var requests = this.requests = [];
1321+
1322+
this.xhr.onCreate = function (xhr) {
1323+
requests.push(xhr);
1324+
};
1325+
});
1326+
1327+
afterEach(function() {
1328+
this.xhr.restore();
1329+
});
1330+
1331+
it('should create an XMLHttpRequest object with body as JSON payload', function() {
1332+
makeXhrRequest({
1333+
url: 'http://localhost/',
1334+
auth: {a: '1', b: '2'},
1335+
data: {foo: 'bar'},
1336+
options: globalOptions
1337+
});
1338+
1339+
var lastXhr = this.requests[this.requests.length - 1];
1340+
assert.equal(lastXhr.requestBody, '{"foo":"bar"}');
1341+
assert.equal(lastXhr.url, 'http://localhost/?a=1&b=2');
1342+
});
1343+
});
1344+
1345+
describe('makeImageRequest', function() {
12701346
var imageCache;
12711347

12721348
beforeEach(function () {
@@ -1275,7 +1351,7 @@ describe('globals', function() {
12751351
})
12761352

12771353
it('should load an Image', function() {
1278-
makeRequest({
1354+
makeImageRequest({
12791355
url: 'http://localhost/',
12801356
auth: {a: '1', b: '2'},
12811357
data: {foo: 'bar'},
@@ -1289,7 +1365,7 @@ describe('globals', function() {
12891365
globalOptions = {
12901366
crossOrigin: 'something'
12911367
};
1292-
makeRequest({
1368+
makeImageRequest({
12931369
url: globalServer,
12941370
auth: {lol: '1'},
12951371
data: {foo: 'bar'},
@@ -1303,7 +1379,7 @@ describe('globals', function() {
13031379
globalOptions = {
13041380
crossOrigin: ''
13051381
};
1306-
makeRequest({
1382+
makeImageRequest({
13071383
url: globalServer,
13081384
auth: {lol: '1'},
13091385
data: {foo: 'bar'},
@@ -1317,7 +1393,7 @@ describe('globals', function() {
13171393
globalOptions = {
13181394
crossOrigin: false
13191395
};
1320-
makeRequest({
1396+
makeImageRequest({
13211397
url: globalServer,
13221398
auth: {lol: '1'},
13231399
data: {foo: 'bar'},
@@ -1972,11 +2048,11 @@ describe('Raven (public API)', function() {
19722048

19732049
it('should work as advertised #integration', function() {
19742050
var imageCache = [];
1975-
this.sinon.stub(window, 'newImage', function(){ var img = {}; imageCache.push(img); return img; });
2051+
this.sinon.stub(window, 'makeRequest');
19762052

19772053
setupRaven();
19782054
Raven.captureMessage('lol', {foo: 'bar'});
1979-
assert.equal(imageCache.length, 1);
2055+
assert.equal(window.makeRequest.callCount, 1);
19802056
// It'd be hard to assert the actual payload being sent
19812057
// since it includes the generated url, which is going to
19822058
// vary between users running the tests

0 commit comments

Comments
 (0)