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 ReactiveX#1732, ReactiveX#1711
  • Loading branch information
kwonoj committed Jun 9, 2016
1 parent 01df713 commit 97ca7e8
Show file tree
Hide file tree
Showing 4 changed files with 125 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
86 changes: 86 additions & 0 deletions spec/observables/dom/ajax-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,105 @@ declare const global: any;
describe('Observable.ajax', () => {
let gXHR: XMLHttpRequest;
let rXHR: XMLHttpRequest;
let xDomain: any;

beforeEach(() => {
gXHR = global.XMLHttpRequest;
rXHR = root.XMLHttpRequest;
xDomain = root.XDomainRequest;

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

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

global.XMLHttpRequest = gXHR;
root.XMLHttpRequest = rXHR;
root.XDomainRequest = xDomain;
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 axObject = new MockXMLHttpRequest();
root.ActiveXObject = () => axObject;
root.XMLHttpRequest = null;

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

Rx.Observable.ajax(obj).subscribe();
expect(MockXMLHttpRequest.mostRecent).to.be.equal(axObject);
});

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', () => {
root.XMLHttpRequest = null;

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

Rx.Observable.ajax(obj).subscribe();
expect(MockXMLHttpRequest.mostRecent).to.exist;
});

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 97ca7e8

Please sign in to comment.