Skip to content

Commit

Permalink
fix(AjaxObservable): support withCredentials for CORS request
Browse files Browse the repository at this point in the history
- AjaxRequest now support setting withCredentials flag

closes #1732, #1711
  • Loading branch information
kwonoj committed Jun 13, 2016
1 parent 0a6c4e8 commit 8084572
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 14 deletions.
1 change: 1 addition & 0 deletions spec/helpers/ajax-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export class MockXMLHttpRequest {
method: any;
data: any;
requestHeaders: any = {};
withCredentials: boolean = false;

constructor() {
this.previousRequest = MockXMLHttpRequest.recentRequest;
Expand Down
93 changes: 93 additions & 0 deletions spec/observables/dom/ajax-spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {expect} from 'chai';
import * as sinon from 'sinon';
import * as Rx from '../../../dist/cjs/Rx';
import {root} from '../../../dist/cjs/util/root';
import {MockXMLHttpRequest} from '../../helpers/ajax-helper';
Expand All @@ -10,18 +11,110 @@ describe('Observable.ajax', () => {
let gXHR: XMLHttpRequest;
let rXHR: XMLHttpRequest;

let sandbox: sinon.SinonSandbox;

beforeEach(() => {
sandbox = sinon.sandbox.create();
gXHR = global.XMLHttpRequest;
rXHR = root.XMLHttpRequest;

global.XMLHttpRequest = MockXMLHttpRequest;
root.XMLHttpRequest = MockXMLHttpRequest;
});

afterEach(() => {
sandbox.restore();
MockXMLHttpRequest.clearRequest();

global.XMLHttpRequest = gXHR;
root.XMLHttpRequest = rXHR;

root.XDomainRequest = null;
root.ActiveXObject = null;
});

it('should create default XMLHttpRequest for non CORS', () => {
const obj: Rx.AjaxRequest = {
url: '/',
method: ''
};

Rx.Observable.ajax(obj).subscribe();
expect(MockXMLHttpRequest.mostRecent.withCredentials).to.be.false;
});

it('should try to create AXObject for XHR in old version of IE', () => {
const axObjectStub = sandbox.stub();
axObjectStub.returns(sinon.stub(new MockXMLHttpRequest()));
root.ActiveXObject = axObjectStub;
root.XMLHttpRequest = null;

const obj: Rx.AjaxRequest = {
url: '/',
method: ''
};

Rx.Observable.ajax(obj).subscribe();
expect(axObjectStub).to.have.been.called;
});

it('should throw if not able to create XMLHttpRequest', () => {
root.XMLHttpRequest = null;
root.ActiveXObject = null;

const obj: Rx.AjaxRequest = {
url: '/',
method: ''
};

expect(() => {
Rx.Observable.ajax(obj).subscribe();
}).to.throw();
});

it('should create XMLHttpRequest for CORS', () => {
const obj: Rx.AjaxRequest = {
url: '/',
method: '',
crossDomain: true,
withCredentials: true
};

Rx.Observable.ajax(obj).subscribe();
expect(MockXMLHttpRequest.mostRecent.withCredentials).to.be.true;
});

it('should try to create XDomainRequest for CORS if XMLHttpRequest is not available', () => {
const xDomainStub = sandbox.stub();
xDomainStub.returns(sinon.stub(new MockXMLHttpRequest()));
root.XDomainRequest = xDomainStub;
root.XMLHttpRequest = null;

const obj: Rx.AjaxRequest = {
url: '/',
method: '',
crossDomain: true,
withCredentials: true
};

Rx.Observable.ajax(obj).subscribe();
expect(xDomainStub).to.have.been.called;
});

it('should throw if not able to create CORS request', () => {
root.XMLHttpRequest = null;
root.XDomainRequest = null;

const obj: Rx.AjaxRequest = {
url: '/',
method: '',
crossDomain: true,
withCredentials: true
};

expect(() => {
Rx.Observable.ajax(obj).subscribe();
}).to.throw();
});

it('should set headers', () => {
Expand Down
2 changes: 1 addition & 1 deletion spec/support/default.opts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
--bail
--full-trace
--check-leaks
--globals WebSocket,FormData
--globals WebSocket,FormData,XDomainRequest,ActiveXObject

--recursive
--timeout 5000
50 changes: 37 additions & 13 deletions src/observable/dom/AjaxObservable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,48 @@ export interface AjaxRequest {
password?: string;
hasContent?: boolean;
crossDomain?: boolean;
withCredentials?: boolean;
createXHR?: () => XMLHttpRequest;
progressSubscriber?: Subscriber<any>;
resultSelector?: <T>(response: AjaxResponse) => T;
responseType?: string;
}

function createXHRDefault(): XMLHttpRequest {
let xhr = new root.XMLHttpRequest();
if (this.crossDomain) {
function getCORSRequest(): XMLHttpRequest {
if (root.XMLHttpRequest) {
const xhr = new root.XMLHttpRequest();
if ('withCredentials' in xhr) {
xhr.withCredentials = true;
return xhr;
} else if (!!root.XDomainRequest) {
return new root.XDomainRequest();
} else {
throw new Error('CORS is not supported by your browser');
xhr.withCredentials = !!this.withCredentials;
}
} else {
return xhr;
} else if (!!root.XDomainRequest) {
return new root.XDomainRequest();
} else {
throw new Error('CORS is not supported by your browser');
}
}

function getXMLHttpRequest(): XMLHttpRequest {
if (root.XMLHttpRequest) {
return new root.XMLHttpRequest();
} else {
let progId: string;
try {
const progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'];
for (let i = 0; i < 3; i++) {
try {
progId = progIds[i];
if (new root.ActiveXObject(progId)) {
break;
}
} catch (e) {
//suppress exceptions
}
}
return new root.ActiveXObject(progId);
} catch (e) {
throw new Error('XMLHttpRequest is not supported by your browser');
}
}
}

Expand Down Expand Up @@ -104,8 +127,6 @@ export class AjaxObservable<T> extends Observable<T> {
* @name ajax
* @owner Observable
*/
static _create_stub(): void { return null; }

static create: AjaxCreationMethod = (() => {
const create: any = (urlOrRequest: string | AjaxRequest) => {
return new AjaxObservable(urlOrRequest);
Expand All @@ -127,8 +148,11 @@ export class AjaxObservable<T> extends Observable<T> {

const request: AjaxRequest = {
async: true,
createXHR: createXHRDefault,
createXHR: function() {
return this.crossDomain ? getCORSRequest.call(this) : getXMLHttpRequest();
},
crossDomain: false,
withCredentials: false,
headers: {},
method: 'GET',
responseType: 'json',
Expand Down

0 comments on commit 8084572

Please sign in to comment.