diff --git a/package-lock.json b/package-lock.json index f76f31c91f3..03186d73b2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,7 +50,7 @@ "npm-run-all": "4.1.2", "opn-cli": "3.1.0", "platform": "1.3.5", - "prettier": "^2.0.5", + "prettier": "^2.5.1", "promise": "8.0.1", "rollup": "0.66.6", "rollup-plugin-alias": "1.4.0", @@ -8884,9 +8884,9 @@ } }, "node_modules/prettier": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", - "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", "dev": true, "bin": { "prettier": "bin-prettier.js" @@ -19870,9 +19870,9 @@ "dev": true }, "prettier": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", - "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", "dev": true }, "process": { diff --git a/package.json b/package.json index 8021c9b514a..aa678f0bd3c 100644 --- a/package.json +++ b/package.json @@ -181,7 +181,7 @@ "npm-run-all": "4.1.2", "opn-cli": "3.1.0", "platform": "1.3.5", - "prettier": "^2.0.5", + "prettier": "^2.5.1", "promise": "8.0.1", "rollup": "0.66.6", "rollup-plugin-alias": "1.4.0", diff --git a/spec/observables/dom/ajax-spec.ts b/spec/observables/dom/ajax-spec.ts index a3457f2d9ae..3e0d3b9e805 100644 --- a/spec/observables/dom/ajax-spec.ts +++ b/spec/observables/dom/ajax-spec.ts @@ -208,6 +208,33 @@ describe('ajax', () => { }); }); + it('should NOT set the X-Requested-With if crossDomain is true', () => { + ajax({ + url: '/test/monkey', + method: 'GET', + crossDomain: true, + }).subscribe(); + + const request = MockXMLHttpRequest.mostRecent; + + expect(request.requestHeaders).to.not.have.key('x-requested-with'); + }); + + it('should not alter user-provided X-Requested-With header, even if crossDomain is true', () => { + ajax({ + url: '/test/monkey', + method: 'GET', + crossDomain: true, + headers: { + 'x-requested-with': 'Custom-XMLHttpRequest', + }, + }).subscribe(); + + const request = MockXMLHttpRequest.mostRecent; + + expect(request.requestHeaders['x-requested-with']).to.equal('Custom-XMLHttpRequest'); + }); + it('should not set default Content-Type header when no body is sent', () => { const obj: AjaxConfig = { url: '/talk-to-me-goose', @@ -1025,7 +1052,7 @@ describe('ajax', () => { const request = { async: true, body: undefined, - crossDomain: true, + crossDomain: false, headers: { 'x-requested-with': 'XMLHttpRequest', }, @@ -1153,7 +1180,7 @@ describe('ajax', () => { const request = { async: true, body: undefined, - crossDomain: true, + crossDomain: false, headers: { 'x-requested-with': 'XMLHttpRequest', }, diff --git a/src/internal/ajax/ajax.ts b/src/internal/ajax/ajax.ts index e54e2cbd4c7..6e4956d3cca 100644 --- a/src/internal/ajax/ajax.ts +++ b/src/internal/ajax/ajax.ts @@ -274,14 +274,23 @@ const LOADSTART = 'loadstart'; const PROGRESS = 'progress'; const LOAD = 'load'; -export function fromAjax(config: AjaxConfig): Observable> { +export function fromAjax(init: AjaxConfig): Observable> { return new Observable((destination) => { - // Here we're pulling off each of the configuration arguments - // that we don't want to add to the request information we're - // passing around. - const { queryParams, body: configuredBody, headers: configuredHeaders, ...remainingConfig } = config; + const config = { + // Defaults + async: true, + crossDomain: false, + withCredentials: false, + method: 'GET', + timeout: 0, + responseType: 'json' as XMLHttpRequestResponseType, + + ...init, + }; + + const { queryParams, body: configuredBody, headers: configuredHeaders } = config; - let { url } = remainingConfig; + let url = config.url; if (!url) { throw new TypeError('url is required'); } @@ -327,6 +336,8 @@ export function fromAjax(config: AjaxConfig): Observable> { } } + const crossDomain = config.crossDomain; + // Set the x-requested-with header. This is a non-standard header that has // come to be a de facto standard for HTTP requests sent by libraries and frameworks // using XHR. However, we DO NOT want to set this if it is a CORS request. This is @@ -334,14 +345,14 @@ export function fromAjax(config: AjaxConfig): Observable> { // None of this is necessary, it's only being set because it's "the thing libraries do" // Starting back as far as JQuery, and continuing with other libraries such as Angular 1, // Axios, et al. - if (!config.crossDomain && !('x-requested-with' in headers)) { + if (!crossDomain && !('x-requested-with' in headers)) { headers['x-requested-with'] = 'XMLHttpRequest'; } // Allow users to provide their XSRF cookie name and the name of a custom header to use to // send the cookie. - const { withCredentials, xsrfCookieName, xsrfHeaderName } = remainingConfig; - if ((withCredentials || !remainingConfig.crossDomain) && xsrfCookieName && xsrfHeaderName) { + const { withCredentials, xsrfCookieName, xsrfHeaderName } = config; + if ((withCredentials || !crossDomain) && xsrfCookieName && xsrfHeaderName) { const xsrfCookie = document?.cookie.match(new RegExp(`(^|;\\s*)(${xsrfCookieName})=([^;]*)`))?.pop() ?? ''; if (xsrfCookie) { headers[xsrfHeaderName] = xsrfCookie; @@ -352,17 +363,9 @@ export function fromAjax(config: AjaxConfig): Observable> { // and set the content-type in `headers`, if we're able. const body = extractContentTypeAndMaybeSerializeBody(configuredBody, headers); - const _request: AjaxRequest = { - // Default values - async: true, - crossDomain: true, - withCredentials: false, - method: 'GET', - timeout: 0, - responseType: 'json' as XMLHttpRequestResponseType, - - // Override with passed user values - ...remainingConfig, + // The final request settings. + const _request: Readonly = { + ...config, // Set values we ensured above url, @@ -373,7 +376,7 @@ export function fromAjax(config: AjaxConfig): Observable> { let xhr: XMLHttpRequest; // Create our XHR so we can get started. - xhr = config.createXHR ? config.createXHR() : new XMLHttpRequest(); + xhr = init.createXHR ? init.createXHR() : new XMLHttpRequest(); { /////////////////////////////////////////////////// @@ -383,7 +386,7 @@ export function fromAjax(config: AjaxConfig): Observable> { // Otherwise the progress events will not fire. /////////////////////////////////////////////////// - const { progressSubscriber, includeDownloadProgress = false, includeUploadProgress = false } = config; + const { progressSubscriber, includeDownloadProgress = false, includeUploadProgress = false } = init; /** * Wires up an event handler that will emit an error when fired. Used diff --git a/src/internal/ajax/types.ts b/src/internal/ajax/types.ts index 0d932e300ed..96e8a911090 100644 --- a/src/internal/ajax/types.ts +++ b/src/internal/ajax/types.ts @@ -133,6 +133,14 @@ export interface AjaxConfig { /** * Whether or not to send the HTTP request as a CORS request. * Defaults to `false`. + * + * @deprecated Will be removed in version 8. Cross domain requests and what creates a cross + * domain request, are dictated by the browser, and a boolean that forces it to be cross domain + * does not make sense. If you need to force cross domain, make sure you're making a secure request, + * then add a custom header to the request or use `withCredentials`. For more information on what + * triggers a cross domain request, see the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials). + * In particular, the section on [Simple Requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Simple_requests) is useful + * for understanding when CORS will not be used. */ crossDomain?: boolean;