Skip to content

Commit 7fd0575

Browse files
authoredJan 11, 2022
fix(ajax): crossDomain flag deprecated and properly reported to consumers (#6710)
* fix(ajax): crossDomain flag deprecated and properly reported to consumers Fixes an issue where the `crosDomain` flag was being incorrectly reported to users via error objects and response objects as defaulting to `true`, when it was in fact defaulting to `false`. Deprecates the `crossDomain` flag in favor of allowing the configuration of the request and the browser to dictate whether or not a preflight request is necessary. Adds deprecation messages with advice about how to force CORS preflights. Ultimately, the boolean flag does not make sense, as there are a lot of factors that dictate CORS preflight use and they may vary by browser/environment. Resolves #6663 * chore: Update side effects file * chore: update side effects files
1 parent 5e8ab00 commit 7fd0575

File tree

6 files changed

+12873
-44
lines changed

6 files changed

+12873
-44
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
import "tslib";
1+

‎package-lock.json

+12,809-18
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@
180180
"npm-run-all": "4.1.2",
181181
"opn-cli": "3.1.0",
182182
"platform": "1.3.5",
183-
"prettier": "^2.0.5",
183+
"prettier": "^2.5.1",
184184
"promise": "8.0.1",
185185
"rollup": "0.66.6",
186186
"rollup-plugin-alias": "1.4.0",

‎spec/observables/dom/ajax-spec.ts

+29-2
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,33 @@ describe('ajax', () => {
208208
});
209209
});
210210

211+
it('should NOT set the X-Requested-With if crossDomain is true', () => {
212+
ajax({
213+
url: '/test/monkey',
214+
method: 'GET',
215+
crossDomain: true,
216+
}).subscribe();
217+
218+
const request = MockXMLHttpRequest.mostRecent;
219+
220+
expect(request.requestHeaders).to.not.have.key('x-requested-with');
221+
});
222+
223+
it('should not alter user-provided X-Requested-With header, even if crossDomain is true', () => {
224+
ajax({
225+
url: '/test/monkey',
226+
method: 'GET',
227+
crossDomain: true,
228+
headers: {
229+
'x-requested-with': 'Custom-XMLHttpRequest',
230+
},
231+
}).subscribe();
232+
233+
const request = MockXMLHttpRequest.mostRecent;
234+
235+
expect(request.requestHeaders['x-requested-with']).to.equal('Custom-XMLHttpRequest');
236+
});
237+
211238
it('should not set default Content-Type header when no body is sent', () => {
212239
const obj: AjaxConfig = {
213240
url: '/talk-to-me-goose',
@@ -1025,7 +1052,7 @@ describe('ajax', () => {
10251052
const request = {
10261053
async: true,
10271054
body: undefined,
1028-
crossDomain: true,
1055+
crossDomain: false,
10291056
headers: {
10301057
'x-requested-with': 'XMLHttpRequest',
10311058
},
@@ -1153,7 +1180,7 @@ describe('ajax', () => {
11531180
const request = {
11541181
async: true,
11551182
body: undefined,
1156-
crossDomain: true,
1183+
crossDomain: false,
11571184
headers: {
11581185
'x-requested-with': 'XMLHttpRequest',
11591186
},

‎src/internal/ajax/ajax.ts

+25-22
Original file line numberDiff line numberDiff line change
@@ -292,14 +292,23 @@ const LOADSTART = 'loadstart';
292292
const PROGRESS = 'progress';
293293
const LOAD = 'load';
294294

295-
export function fromAjax<T>(config: AjaxConfig): Observable<AjaxResponse<T>> {
295+
export function fromAjax<T>(init: AjaxConfig): Observable<AjaxResponse<T>> {
296296
return new Observable((destination) => {
297-
// Here we're pulling off each of the configuration arguments
298-
// that we don't want to add to the request information we're
299-
// passing around.
300-
const { queryParams, body: configuredBody, headers: configuredHeaders, ...remainingConfig } = config;
297+
const config = {
298+
// Defaults
299+
async: true,
300+
crossDomain: false,
301+
withCredentials: false,
302+
method: 'GET',
303+
timeout: 0,
304+
responseType: 'json' as XMLHttpRequestResponseType,
305+
306+
...init,
307+
};
308+
309+
const { queryParams, body: configuredBody, headers: configuredHeaders } = config;
301310

302-
let { url } = remainingConfig;
311+
let url = config.url;
303312
if (!url) {
304313
throw new TypeError('url is required');
305314
}
@@ -345,21 +354,23 @@ export function fromAjax<T>(config: AjaxConfig): Observable<AjaxResponse<T>> {
345354
}
346355
}
347356

357+
const crossDomain = config.crossDomain;
358+
348359
// Set the x-requested-with header. This is a non-standard header that has
349360
// come to be a de facto standard for HTTP requests sent by libraries and frameworks
350361
// using XHR. However, we DO NOT want to set this if it is a CORS request. This is
351362
// because sometimes this header can cause issues with CORS. To be clear,
352363
// None of this is necessary, it's only being set because it's "the thing libraries do"
353364
// Starting back as far as JQuery, and continuing with other libraries such as Angular 1,
354365
// Axios, et al.
355-
if (!config.crossDomain && !('x-requested-with' in headers)) {
366+
if (!crossDomain && !('x-requested-with' in headers)) {
356367
headers['x-requested-with'] = 'XMLHttpRequest';
357368
}
358369

359370
// Allow users to provide their XSRF cookie name and the name of a custom header to use to
360371
// send the cookie.
361-
const { withCredentials, xsrfCookieName, xsrfHeaderName } = remainingConfig;
362-
if ((withCredentials || !remainingConfig.crossDomain) && xsrfCookieName && xsrfHeaderName) {
372+
const { withCredentials, xsrfCookieName, xsrfHeaderName } = config;
373+
if ((withCredentials || !crossDomain) && xsrfCookieName && xsrfHeaderName) {
363374
const xsrfCookie = document?.cookie.match(new RegExp(`(^|;\\s*)(${xsrfCookieName})=([^;]*)`))?.pop() ?? '';
364375
if (xsrfCookie) {
365376
headers[xsrfHeaderName] = xsrfCookie;
@@ -370,17 +381,9 @@ export function fromAjax<T>(config: AjaxConfig): Observable<AjaxResponse<T>> {
370381
// and set the content-type in `headers`, if we're able.
371382
const body = extractContentTypeAndMaybeSerializeBody(configuredBody, headers);
372383

373-
const _request: AjaxRequest = {
374-
// Default values
375-
async: true,
376-
crossDomain: true,
377-
withCredentials: false,
378-
method: 'GET',
379-
timeout: 0,
380-
responseType: 'json' as XMLHttpRequestResponseType,
381-
382-
// Override with passed user values
383-
...remainingConfig,
384+
// The final request settings.
385+
const _request: Readonly<AjaxRequest> = {
386+
...config,
384387

385388
// Set values we ensured above
386389
url,
@@ -391,7 +394,7 @@ export function fromAjax<T>(config: AjaxConfig): Observable<AjaxResponse<T>> {
391394
let xhr: XMLHttpRequest;
392395

393396
// Create our XHR so we can get started.
394-
xhr = config.createXHR ? config.createXHR() : new XMLHttpRequest();
397+
xhr = init.createXHR ? init.createXHR() : new XMLHttpRequest();
395398

396399
{
397400
///////////////////////////////////////////////////
@@ -401,7 +404,7 @@ export function fromAjax<T>(config: AjaxConfig): Observable<AjaxResponse<T>> {
401404
// Otherwise the progress events will not fire.
402405
///////////////////////////////////////////////////
403406

404-
const { progressSubscriber, includeDownloadProgress = false, includeUploadProgress = false } = config;
407+
const { progressSubscriber, includeDownloadProgress = false, includeUploadProgress = false } = init;
405408

406409
/**
407410
* Wires up an event handler that will emit an error when fired. Used

‎src/internal/ajax/types.ts

+8
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,14 @@ export interface AjaxConfig {
133133
/**
134134
* Whether or not to send the HTTP request as a CORS request.
135135
* Defaults to `false`.
136+
*
137+
* @deprecated Will be removed in version 8. Cross domain requests and what creates a cross
138+
* domain request, are dictated by the browser, and a boolean that forces it to be cross domain
139+
* does not make sense. If you need to force cross domain, make sure you're making a secure request,
140+
* then add a custom header to the request or use `withCredentials`. For more information on what
141+
* triggers a cross domain request, see the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials).
142+
* In particular, the section on [Simple Requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Simple_requests) is useful
143+
* for understanding when CORS will not be used.
136144
*/
137145
crossDomain?: boolean;
138146

0 commit comments

Comments
 (0)
Please sign in to comment.