diff --git a/package-lock.json b/package-lock.json index 7d226f6a1b..fa67ec90d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1667,7 +1667,7 @@ "babylon": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha1-ry87iPpvXB5MY00aD46sT1WzleM=", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", "dev": true }, "backo2": { @@ -2429,7 +2429,7 @@ "browserify-zlib": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha1-KGlFnZqjviRf6P4sofRuLn9U1z8=", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", "dev": true, "requires": { "pako": "~1.0.5" @@ -2985,7 +2985,7 @@ "cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha1-h2Dk7MJy9MNjUy+SbYdKriwTl94=", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", "dev": true, "requires": { "inherits": "^2.0.1", @@ -3585,7 +3585,7 @@ "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", "dev": true }, "conventional-changelog": { @@ -5034,7 +5034,7 @@ "crypto-browserify": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha1-OWz58xN/A+S45TLFj2mCVOAPgOw=", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", "dev": true, "requires": { "browserify-cipher": "^1.0.0", @@ -5478,7 +5478,7 @@ "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha1-PTH1AZGmdJ3RN1p/Ui6CPULlTto=", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true }, "domelementtype": { @@ -5949,7 +5949,7 @@ "evp_bytestokey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha1-f8vbGY3HGVlDLv4ThCaE4FJaywI=", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", "dev": true, "requires": { "md5.js": "^1.3.4", @@ -7515,7 +7515,7 @@ "globals": { "version": "9.18.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", "dev": true }, "globby": { @@ -7649,7 +7649,7 @@ "has-binary2": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha1-d3asYn8+p3JQz8My2rfd9eT10R0=", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", "dev": true, "requires": { "isarray": "2.0.1" @@ -8890,7 +8890,7 @@ "is-number-like": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/is-number-like/-/is-number-like-1.0.8.tgz", - "integrity": "sha1-LhKWILUIkQQuROm7uzBZPnXPu+M=", + "integrity": "sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA==", "dev": true, "requires": { "lodash.isfinite": "^3.3.2" @@ -10997,7 +10997,7 @@ "miller-rabin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha1-8IA1HIZbDcViqEYpZtqlNUPHik0=", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", "dev": true, "requires": { "bn.js": "^4.0.0", @@ -13037,7 +13037,7 @@ "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha1-I4Hts2ifelPWUxkAYPz4ItLzaP8=", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", "dev": true }, "process": { @@ -13142,7 +13142,7 @@ "qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha1-xF6cYYAL0IfviNfiVkI73Unl0HE=", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", "dev": true }, "qs": { @@ -13210,7 +13210,7 @@ "randomfill": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha1-ySGW/IarQr6YPxvzF3giSTHWFFg=", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", "dev": true, "requires": { "randombytes": "^2.0.5", @@ -13406,7 +13406,7 @@ "regenerator-transform": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", - "integrity": "sha1-HkmWg3Ix2ot/PPQRTXG1aRoGgN0=", + "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", "dev": true, "requires": { "babel-runtime": "^6.18.0", @@ -13502,7 +13502,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "through2": { @@ -14156,7 +14156,7 @@ "send": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha1-bsyh4PjBVtFBWXVZhI32RzCmu8E=", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", "dev": true, "requires": { "debug": "2.6.9", @@ -14204,7 +14204,7 @@ "mime": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha1-Eh+evEnjdm8xGnbh+hyAA8SwOqY=", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", "dev": true }, "ms": { @@ -14222,7 +14222,7 @@ "statuses": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha1-u3PURtonlhBu/MG2AaJT1sRr0Ic=", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", "dev": true } } @@ -14298,7 +14298,7 @@ "serve-static": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha1-CV6Ecv1bRiN9tQzkhqQ/S4bGzsE=", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", "dev": true, "requires": { "encodeurl": "~1.0.2", @@ -16396,7 +16396,7 @@ "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha1-YQY29rH3A4kb00dxzLF/uTtHB5w=", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, "wordwrap": { diff --git a/packages/arcgis-rest-auth/src/app-tokens.ts b/packages/arcgis-rest-auth/src/app-tokens.ts index 3d3953b171..a83919b7e0 100644 --- a/packages/arcgis-rest-auth/src/app-tokens.ts +++ b/packages/arcgis-rest-auth/src/app-tokens.ts @@ -79,9 +79,8 @@ export function platformSelf( "X-Esri-Auth-Client-Id": clientId, "X-Esri-Auth-Redirect-Uri": redirectUri, }, - // We need to ensure the cookies are sent as that's - // what the API uses to convert things - credentials: "include", + // Note: request has logic to include the cookie + // for platformSelf calls w/ the X-Esri-Auth-Client-Id header params: { f: "json", }, diff --git a/packages/arcgis-rest-auth/test/app-tokens.test.ts b/packages/arcgis-rest-auth/test/app-tokens.test.ts index 6d27d765fc..6127a75b49 100644 --- a/packages/arcgis-rest-auth/test/app-tokens.test.ts +++ b/packages/arcgis-rest-auth/test/app-tokens.test.ts @@ -65,6 +65,10 @@ describe("app-token functions: ", () => { expect(headers["X-Esri-Auth-Redirect-Uri"]).toBe( "https://hub.arcgis.com/torii-provider-arcgis/redirect.html" ); + expect(options.credentials).toBe( + "include", + "fetch should send cookie" + ); expect(headers["X-Esri-Auth-Client-Id"]).toBe("CLIENT-ID-ABC123"); expect(response.token).toEqual("APP-TOKEN"); expect(response.username).toEqual("jsmith"); diff --git a/packages/arcgis-rest-request/src/request.ts b/packages/arcgis-rest-request/src/request.ts index 39f8ec0146..da94dbc7bd 100644 --- a/packages/arcgis-rest-request/src/request.ts +++ b/packages/arcgis-rest-request/src/request.ts @@ -15,8 +15,8 @@ export const NODEJS_DEFAULT_REFERER_HEADER = `@esri/arcgis-rest-js`; let DEFAULT_ARCGIS_REQUEST_OPTIONS: IRequestOptions = { httpMethod: "POST", params: { - f: "json" - } + f: "json", + }, }; /** @@ -74,19 +74,19 @@ export class ArcGISAuthError extends ArcGISRequestError { const retryRequest = (resolve: any, reject: any) => { getSession(this.url, this.options) - .then(session => { + .then((session) => { const newOptions = { ...this.options, - ...{ authentication: session } + ...{ authentication: session }, }; tries = tries + 1; return request(this.url, newOptions); }) - .then(response => { + .then((response) => { resolve(response); }) - .catch(e => { + .catch((e) => { if (e.name === "ArcGISAuthError" && tries < retryLimit) { retryRequest(resolve, reject); } else if (e.name === "ArcGISAuthError" && tries >= retryLimit) { @@ -197,13 +197,13 @@ export function request( ...{ params: { ...DEFAULT_ARCGIS_REQUEST_OPTIONS.params, - ...requestOptions.params + ...requestOptions.params, }, headers: { ...DEFAULT_ARCGIS_REQUEST_OPTIONS.headers, - ...requestOptions.headers - } - } + ...requestOptions.headers, + }, + }, }; const missingGlobals: string[] = []; @@ -245,7 +245,7 @@ export function request( const params: IParams = { ...{ f: "json" }, - ...options.params + ...options.params, }; let originalAuthError: ArcGISAuthError = null; @@ -254,11 +254,22 @@ export function request( method: httpMethod, /* ensures behavior mimics XMLHttpRequest. needed to support sending IWA cookies */ - credentials: "same-origin" + credentials: "same-origin", }; + // the /oauth2/platformSelf route will add X-Esri-Auth-Client-Id header + // and that request needs to send cookies cross domain + // so we need to set the credentials to "include" + if ( + options.headers && + options.headers["X-Esri-Auth-Client-Id"] && + url.indexOf("/oauth2/platformSelf") > -1 + ) { + fetchOptions.credentials = "include"; + } + return (authentication - ? authentication.getToken(url, { fetch: options.fetch }).catch(err => { + ? authentication.getToken(url, { fetch: options.fetch }).catch((err) => { /** * append original request url and requestOptions * to the error thrown by getToken() @@ -276,7 +287,7 @@ export function request( }) : Promise.resolve("") ) - .then(token => { + .then((token) => { if (token.length) { params.token = token; } @@ -285,14 +296,17 @@ export function request( const requestHeaders: { [key: string]: any; } = {}; - + if (fetchOptions.method === "GET") { // Prevents token from being passed in query params when hideToken option is used. /* istanbul ignore if - window is always defined in a browser. Test case is covered by Jasmine in node test */ - if (params.token && options.hideToken && + if ( + params.token && + options.hideToken && // Sharing API does not support preflight check required by modern browsers https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request - typeof window === 'undefined') { - requestHeaders["X-Esri-Authorization"] = `Bearer ${params.token}` + typeof window === "undefined" + ) { + requestHeaders["X-Esri-Authorization"] = `Bearer ${params.token}`; delete params.token; } // encode the parameters into the query string @@ -304,7 +318,7 @@ export function request( if ( // This would exceed the maximum length for URLs specified by the consumer and requires POST (options.maxUrlLength && - urlWithQueryString.length > options.maxUrlLength) || + urlWithQueryString.length > options.maxUrlLength) || // Or if the customer requires the token to be hidden and it has not already been hidden in the header (for browsers) (params.token && options.hideToken) ) { @@ -336,7 +350,7 @@ export function request( // Mixin headers from request options fetchOptions.headers = { ...requestHeaders, - ...options.headers + ...options.headers, }; /* istanbul ignore next - karma reports coverage on browser tests only */ @@ -352,7 +366,7 @@ export function request( return options.fetch(url, fetchOptions); }) - .then(response => { + .then((response) => { if (!response.ok) { // server responded w/ an actual error (404, 500, etc) const { status, statusText } = response; @@ -381,7 +395,7 @@ export function request( return response.blob(); } }) - .then(data => { + .then((data) => { if ((params.f === "json" || params.f === "geojson") && !rawResponse) { const response = checkForErrors( data, @@ -401,7 +415,7 @@ export function request( (options.authentication as any).trustedServers[truncatedUrl] = { token: [], // default to 24 hours - expires: new Date(Date.now() + 86400 * 1000) + expires: new Date(Date.now() + 86400 * 1000), }; originalAuthError = null; } diff --git a/packages/arcgis-rest-request/test/request.test.ts b/packages/arcgis-rest-request/test/request.test.ts index c193abf44d..2688e391ec 100644 --- a/packages/arcgis-rest-request/test/request.test.ts +++ b/packages/arcgis-rest-request/test/request.test.ts @@ -1,11 +1,16 @@ /* Copyright (c) 2018 Environmental Systems Research Institute, Inc. * Apache-2.0 */ -import { request, ErrorTypes, setDefaultRequestOptions } from "../src/index"; +import { + request, + ErrorTypes, + setDefaultRequestOptions, + IRequestOptions, +} from "../src/index"; import * as fetchMock from "fetch-mock"; import { SharingRestInfo, - SharingRestInfoHTML + SharingRestInfoHTML, } from "./mocks/sharing-rest-info"; import { MockParamBuilder } from "./mocks/param-builder"; import { ArcGISOnlineError } from "./mocks/errors"; @@ -15,11 +20,11 @@ import { GeoJSONFeatureCollection } from "./mocks/geojson-feature-collection"; describe("request()", () => { afterEach(fetchMock.restore); - it("should make a basic POST request", done => { + it("should make a basic POST request", (done) => { fetchMock.once("*", SharingRestInfo); request("https://www.arcgis.com/sharing/rest/info") - .then(response => { + .then((response) => { const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); expect(url).toEqual("https://www.arcgis.com/sharing/rest/info"); expect(options.method).toBe("POST"); @@ -27,40 +32,40 @@ describe("request()", () => { expect(options.body).toContain("f=json"); done(); }) - .catch(e => { + .catch((e) => { fail(e); }); }); - it("should make a basic GET request", done => { + it("should make a basic GET request", (done) => { fetchMock.once("*", SharingRestInfo); request("https://www.arcgis.com/sharing/rest/info", { - httpMethod: "GET" + httpMethod: "GET", }) - .then(response => { + .then((response) => { const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); expect(url).toEqual("https://www.arcgis.com/sharing/rest/info?f=json"); expect(options.method).toBe("GET"); expect(response).toEqual(SharingRestInfo); done(); }) - .catch(e => { + .catch((e) => { fail(e); }); }); - it("should make a basic GET request for text", done => { + it("should make a basic GET request for text", (done) => { fetchMock.once("*", WebMapAsText); request( "https://www.arcgis.com/sharing/rest/content/items/43a8e51789044d9480a20089a84129ad/data", { httpMethod: "GET", - params: { f: "text" } + params: { f: "text" }, } ) - .then(response => { + .then((response) => { const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); expect(url).toEqual( "https://www.arcgis.com/sharing/rest/content/items/43a8e51789044d9480a20089a84129ad/data?f=text" @@ -69,41 +74,41 @@ describe("request()", () => { expect(response).toEqual(WebMapAsText); done(); }) - .catch(e => { + .catch((e) => { fail(e); }); }); - it("should make a basic GET request for html", done => { + it("should make a basic GET request for html", (done) => { fetchMock.once("*", SharingRestInfoHTML); request("https://www.arcgis.com/sharing/rest/info", { httpMethod: "GET", - params: { f: "html" } + params: { f: "html" }, }) - .then(response => { + .then((response) => { const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); expect(url).toEqual("https://www.arcgis.com/sharing/rest/info?f=html"); expect(options.method).toBe("GET"); expect(response).toEqual(SharingRestInfoHTML); done(); }) - .catch(e => { + .catch((e) => { fail(e); }); }); - it("should make a basic GET request for geojson", done => { + it("should make a basic GET request for geojson", (done) => { fetchMock.once("*", GeoJSONFeatureCollection); request( "https://services1.arcgis.com/ORG/arcgis/rest/services/FEATURE_SERVICE/FeatureServer/0/query", { httpMethod: "GET", - params: { where: "1=1", f: "geojson" } + params: { where: "1=1", f: "geojson" }, } ) - .then(response => { + .then((response) => { const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); expect(url).toEqual( "https://services1.arcgis.com/ORG/arcgis/rest/services/FEATURE_SERVICE/FeatureServer/0/query?f=geojson&where=1%3D1" @@ -112,12 +117,12 @@ describe("request()", () => { expect(response).toEqual(GeoJSONFeatureCollection); done(); }) - .catch(e => { + .catch((e) => { fail(e); }); }); - it("should switch from GET to POST when url is longer than specified", done => { + it("should switch from GET to POST when url is longer than specified", (done) => { fetchMock.once("*", SharingRestInfo); const restInfoUrl = "https://www.arcgis.com/sharing/rest/info"; @@ -126,9 +131,9 @@ describe("request()", () => { // typically consumers would base maxUrlLength on browser/server limits // but for testing, we use an artificially low limit // like this one that assumes no parameters will be added - maxUrlLength: restInfoUrl.length + maxUrlLength: restInfoUrl.length, }) - .then(response => { + .then((response) => { const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); expect(url).toEqual("https://www.arcgis.com/sharing/rest/info"); expect(options.method).toBe("POST"); @@ -136,28 +141,28 @@ describe("request()", () => { expect(response).toEqual(SharingRestInfo); done(); }) - .catch(e => { + .catch((e) => { fail(e); }); }); - it("should use the `authentication` option to authenticate a request", done => { + it("should use the `authentication` option to authenticate a request", (done) => { fetchMock.once("*", WebMapAsText); const MOCK_AUTH = { portal: "https://www.arcgis.com/sharing/rest", getToken() { return Promise.resolve("token"); - } + }, }; request( "https://www.arcgis.com/sharing/rest/content/items/43a8e51789044d9480a20089a84129ad/data", { - authentication: MOCK_AUTH + authentication: MOCK_AUTH, } ) - .then(response => { + .then((response) => { const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); expect(url).toEqual( "https://www.arcgis.com/sharing/rest/content/items/43a8e51789044d9480a20089a84129ad/data" @@ -166,34 +171,38 @@ describe("request()", () => { expect(response).toEqual(WebMapAsJSON); done(); }) - .catch(e => { + .catch((e) => { fail(e); }); }); - it("should hide token in POST body in browser environments otherwise it should hide token in `X-ESRI_AUTHORIZATION` header in Node", done => { + it("should hide token in POST body in browser environments otherwise it should hide token in `X-ESRI_AUTHORIZATION` header in Node", (done) => { fetchMock.once("*", SharingRestInfo); const MOCK_AUTH = { portal: "https://www.arcgis.com/sharing/rest", getToken() { return Promise.resolve("token"); - } + }, }; request("https://www.arcgis.com/sharing/rest/info", { authentication: MOCK_AUTH, - httpMethod: 'GET', - hideToken: true + httpMethod: "GET", + hideToken: true, }) - .then(response => { + .then((response) => { // Test Node path with Jasmine in Node - if (typeof window === 'undefined') { + if (typeof window === "undefined") { const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); - expect(url).toEqual("https://www.arcgis.com/sharing/rest/info?f=json"); + expect(url).toEqual( + "https://www.arcgis.com/sharing/rest/info?f=json" + ); expect(options.method).toBe("GET"); expect(response).toEqual(SharingRestInfo); - expect((options.headers as any)["X-Esri-Authorization"]).toBe("Bearer token"); + expect((options.headers as any)["X-Esri-Authorization"]).toBe( + "Bearer token" + ); } else { // Test browser path when run in browser with Karma const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); @@ -201,17 +210,56 @@ describe("request()", () => { expect(options.method).toBe("POST"); expect(options.body).toContain("f=json"); expect(options.body).toContain("token=token"); - expect((options.headers as any)["X-Esri-Authorization"]).toBe(undefined); - expect(response).toEqual(SharingRestInfo) + expect((options.headers as any)["X-Esri-Authorization"]).toBe( + undefined + ); + expect(response).toEqual(SharingRestInfo); } done(); }) - .catch(e => { + .catch((e) => { fail(e); }); }); - it("should switch from GET to POST when url is longer than specified and replace token in header with token in POST body", done => { + it("it should set credential to include for platformSelf call w header", () => { + const PLATFORM_SELF_URL = + "https://www.arcgis.com/sharing/rest/oauth2/platformSelf?f=json"; + fetchMock.postOnce(PLATFORM_SELF_URL, { + username: "jsmith", + token: "APP-TOKEN", + }); + const ro = { + method: "POST", + headers: { + "X-Esri-Auth-Client-Id": "CLIENT-ID-ABC123", + "X-Esri-Auth-Redirect-Uri": + "https://hub.arcgis.com/torii-provider-arcgis/redirect.html", + }, + // Note: request has logic to include the cookie + // for platformSelf calls w/ the X-Esri-Auth-Client-Id header + params: { + f: "json", + }, + } as IRequestOptions; + + return request(PLATFORM_SELF_URL, ro).then((response) => { + const [url, options]: [string, RequestInit] = fetchMock.lastCall( + PLATFORM_SELF_URL + ); + expect(url).toEqual(PLATFORM_SELF_URL); + const headers = options.headers || ({} as any); + expect(headers["X-Esri-Auth-Redirect-Uri"]).toBe( + "https://hub.arcgis.com/torii-provider-arcgis/redirect.html" + ); + expect(options.credentials).toBe("include", "fetch should send cookie"); + expect(headers["X-Esri-Auth-Client-Id"]).toBe("CLIENT-ID-ABC123"); + expect(response.token).toEqual("APP-TOKEN"); + expect(response.username).toEqual("jsmith"); + }); + }); + + it("should switch from GET to POST when url is longer than specified and replace token in header with token in POST body", (done) => { fetchMock.once("*", SharingRestInfo); const restInfoUrl = "https://www.arcgis.com/sharing/rest/info"; @@ -219,7 +267,7 @@ describe("request()", () => { portal: "https://www.arcgis.com/sharing/rest", getToken() { return Promise.resolve("token"); - } + }, }; request(restInfoUrl, { @@ -229,29 +277,31 @@ describe("request()", () => { // typically consumers would base maxUrlLength on browser/server limits // but for testing, we use an artificially low limit // like this one that assumes no parameters will be added - maxUrlLength: restInfoUrl.length + maxUrlLength: restInfoUrl.length, }) - .then(response => { + .then((response) => { const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); expect(url).toEqual("https://www.arcgis.com/sharing/rest/info"); expect(options.method).toBe("POST"); expect(options.body).toContain("f=json"); expect(options.body).toContain("token=token"); - expect((options.headers as any)["X-Esri-Authorization"]).toBe(undefined); + expect((options.headers as any)["X-Esri-Authorization"]).toBe( + undefined + ); expect(response).toEqual(SharingRestInfo); done(); }) - .catch(e => { + .catch((e) => { fail(e); }); }); - it("should re-throw HTTP errors (404, 500, etc)", done => { + it("should re-throw HTTP errors (404, 500, etc)", (done) => { fetchMock.once("*", 404); request( "https://www.arcgis.com/sharing/rest/content/items/43a8e51789044d9480a20089a84129ad/data" - ).catch(error => { + ).catch((error) => { expect(error.name).toBe(ErrorTypes.ArcGISRequestError); expect(error.message).toBe("HTTP 404: Not Found"); expect(error instanceof Error).toBeTruthy(); @@ -266,12 +316,12 @@ describe("request()", () => { }); }); - it("should throw errors with information about the request", done => { + it("should throw errors with information about the request", (done) => { fetchMock.once("*", ArcGISOnlineError); request( "https://www.arcgis.com/sharing/rest/content/items/43a8e51789044d9480a20089a84129ad/data" - ).catch(error => { + ).catch((error) => { expect(error.name).toBe(ErrorTypes.ArcGISRequestError); expect(error.message).toBe("400: 'type' and 'title' property required."); expect(error instanceof Error).toBeTruthy(); @@ -286,7 +336,7 @@ describe("request()", () => { }); }); - it("should allow you to use custom implementations of `fetch`", done => { + it("should allow you to use custom implementations of `fetch`", (done) => { const MockFetchResponse = { ok: true, json() { @@ -297,7 +347,7 @@ describe("request()", () => { }, text() { return Promise.resolve(JSON.stringify(SharingRestInfo)); - } + }, }; const MockFetch = function() { @@ -305,18 +355,18 @@ describe("request()", () => { }; request("https://www.arcgis.com/sharing/rest/info", { - fetch: MockFetch as any + fetch: MockFetch as any, }) - .then(response => { + .then((response) => { expect(response).toEqual(SharingRestInfo); done(); }) - .catch(e => { + .catch((e) => { fail(e); }); }); - it("should return a raw response if requested", done => { + it("should return a raw response if requested", (done) => { fetchMock.once("*", GeoJSONFeatureCollection); request( @@ -324,10 +374,10 @@ describe("request()", () => { { httpMethod: "GET", params: { where: "1=1", f: "geojson" }, - rawResponse: true + rawResponse: true, } ) - .then(response => { + .then((response) => { expect(response.status).toBe(200); expect(response.ok).toBe(true); expect(response.body.Readable).not.toBe(null); @@ -338,22 +388,22 @@ describe("request()", () => { // this used to work with isomorphic-fetch // expect(response instanceof Response).toBe(true); }) - .catch(e => { + .catch((e) => { fail(e); }); }); - it("should allow setting defaults for all requests", done => { + it("should allow setting defaults for all requests", (done) => { fetchMock.once("*", SharingRestInfo); setDefaultRequestOptions({ headers: { - "Test-Header": "Test" - } + "Test-Header": "Test", + }, }); request("https://www.arcgis.com/sharing/rest/info") - .then(response => { + .then((response) => { const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); expect(url).toEqual("https://www.arcgis.com/sharing/rest/info"); expect(options.method).toBe("POST"); @@ -362,7 +412,7 @@ describe("request()", () => { expect((options.headers as any)["Test-Header"]).toBe("Test"); done(); }) - .catch(e => { + .catch((e) => { fail(e); }); @@ -370,9 +420,9 @@ describe("request()", () => { setDefaultRequestOptions({ httpMethod: "POST", params: { - f: "json" + f: "json", }, - fetch + fetch, }); }); @@ -387,16 +437,16 @@ describe("request()", () => { portal: "https://www.arcgis.com/sharing/rest", getToken() { return Promise.resolve("token"); - } + }, }; setDefaultRequestOptions({ - authentication: MOCK_AUTH + authentication: MOCK_AUTH, }); setDefaultRequestOptions( { - authentication: MOCK_AUTH + authentication: MOCK_AUTH, }, true ); @@ -407,15 +457,15 @@ describe("request()", () => { setDefaultRequestOptions({ httpMethod: "POST", params: { - f: "json" - } + f: "json", + }, }); console.warn = oldWarn; }); describe("should impliment the IParamBuilder and IParamsBuilder interfaces builder", () => { - it("should encode a param that impliments IParamBuilder", done => { + it("should encode a param that impliments IParamBuilder", (done) => { fetchMock.once("*", GeoJSONFeatureCollection); const builder = new MockParamBuilder(); @@ -423,16 +473,16 @@ describe("request()", () => { request( "https://services1.arcgis.com/ORG/arcgis/rest/services/FEATURE_SERVICE/FeatureServer/0/query", { - params: { where: builder, f: "geojson" } + params: { where: builder, f: "geojson" }, } ) - .then(response => { + .then((response) => { const options: RequestInit = fetchMock.lastCall("*")[1]; expect(options.body).toContain(`where=${encodeURIComponent("1=1")}`); expect(response).toEqual(GeoJSONFeatureCollection); done(); }) - .catch(e => { + .catch((e) => { fail(e); }); }); @@ -462,7 +512,7 @@ describe("request()", () => { ); }); - it("should not throw if fetch is not present but a custom fetch is defined", done => { + it("should not throw if fetch is not present but a custom fetch is defined", (done) => { Promise = oldPromise; FormData = oldFormData; @@ -476,7 +526,7 @@ describe("request()", () => { }, text() { return Promise.resolve(JSON.stringify(SharingRestInfo)); - } + }, }; const MockFetch = function() { @@ -484,20 +534,20 @@ describe("request()", () => { }; request("https://www.arcgis.com/sharing/rest/info", { - fetch: MockFetch as any + fetch: MockFetch as any, }) - .then(response => { + .then((response) => { expect(response).toEqual(SharingRestInfo); done(); }) - .catch(e => { + .catch((e) => { fail(e); }); }); }); if (typeof window === "undefined") { - it("should tack on a generic referer header - in Node.js only", done => { + it("should tack on a generic referer header - in Node.js only", (done) => { fetchMock.once("*", WebMapAsJSON); request("https://www.arcgis.com/sharing/rest/content/items/43a/data") @@ -510,20 +560,20 @@ describe("request()", () => { expect(options.method).toBe("POST"); expect(options.headers).toEqual({ referer: "@esri/arcgis-rest-js", - "Content-Type": "application/x-www-form-urlencoded" + "Content-Type": "application/x-www-form-urlencoded", }); done(); }) - .catch(e => { + .catch((e) => { fail(e); }); }); - it("should use referer header from request options - in Node.js only", done => { + it("should use referer header from request options - in Node.js only", (done) => { fetchMock.once("*", WebMapAsJSON); request("https://www.arcgis.com/sharing/rest/content/items/43a/data", { - headers: { referer: "test/referer" } + headers: { referer: "test/referer" }, }) .then(() => { expect(fetchMock.called()).toEqual(true); @@ -534,20 +584,20 @@ describe("request()", () => { expect(options.method).toBe("POST"); expect(options.headers).toEqual({ referer: "test/referer", - "Content-Type": "application/x-www-form-urlencoded" + "Content-Type": "application/x-www-form-urlencoded", }); done(); }) - .catch(e => { + .catch((e) => { fail(e); }); }); - it("if no referer header is provided, but other headers are passed, a default should still be set - in Node.js only", done => { + it("if no referer header is provided, but other headers are passed, a default should still be set - in Node.js only", (done) => { fetchMock.once("*", WebMapAsJSON); request("https://www.arcgis.com/sharing/rest/content/items/43a/data", { - headers: { foo: "bar" } + headers: { foo: "bar" }, }) .then(() => { expect(fetchMock.called()).toEqual(true); @@ -559,11 +609,11 @@ describe("request()", () => { expect(options.headers).toEqual({ "Content-Type": "application/x-www-form-urlencoded", referer: "@esri/arcgis-rest-js", - foo: "bar" + foo: "bar", }); done(); }) - .catch(e => { + .catch((e) => { fail(e); }); });