@esri/telemetry-amazon 5.1.2 → 5.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2017 Environmental Systems Research Institute, Inc.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -538,6 +538,168 @@
538
538
  }
539
539
  }
540
540
 
541
+ /* istanbul ignore file */
542
+ // Note: currently this is all internal to the package, and we are not exposing
543
+ // anything that a user can set... but we need all this to be able to ensure
544
+ // that multiple instances of the package can share the same config.
545
+ /**
546
+ * The default config for the request module. This is used to store
547
+ * the no-cors domains and pending requests.
548
+ */
549
+ const DEFAULT_ARCGIS_REQUEST_CONFIG = {
550
+ noCorsDomains: [],
551
+ crossOriginNoCorsDomains: {},
552
+ pendingNoCorsRequests: {}
553
+ };
554
+ const GLOBAL_VARIABLE_NAME = "ARCGIS_REST_JS_NO_CORS";
555
+ // Set the global variable to the default config if it is not aleady defined
556
+ // This is done to ensure that all instances of rest-request work with a single
557
+ // instance of the config
558
+ if (!globalThis[GLOBAL_VARIABLE_NAME]) {
559
+ globalThis[GLOBAL_VARIABLE_NAME] = Object.assign({}, DEFAULT_ARCGIS_REQUEST_CONFIG);
560
+ }
561
+ // export the settings as immutable consts that read from the global config
562
+ const requestConfig = globalThis[GLOBAL_VARIABLE_NAME];
563
+
564
+ /**
565
+ * Send a no-cors request to the passed uri. This is used to pick up
566
+ * a cookie from a 3rd party server to meet a requirement of some authentication
567
+ * flows.
568
+ * @param url
569
+ * @returns
570
+ */
571
+ function sendNoCorsRequest(url) {
572
+ // drop any query params, other than f=json
573
+ const urlObj = new URL(url);
574
+ url = urlObj.origin + urlObj.pathname;
575
+ if (urlObj.search.includes("f=json")) {
576
+ url += "?f=json";
577
+ }
578
+ const origin = urlObj.origin;
579
+ // If we have already sent a no-cors request to this url, return the promise
580
+ // so we don't send multiple requests
581
+ if (requestConfig.pendingNoCorsRequests[origin]) {
582
+ return requestConfig.pendingNoCorsRequests[origin];
583
+ }
584
+ // Make the request and add to the cache
585
+ requestConfig.pendingNoCorsRequests[origin] = fetch(url, {
586
+ mode: "no-cors",
587
+ credentials: "include",
588
+ cache: "no-store"
589
+ })
590
+ .then((response) => {
591
+ // Add to the list of cross-origin no-cors domains
592
+ // if the domain is not already in the list
593
+ if (requestConfig.noCorsDomains.indexOf(origin) === -1) {
594
+ requestConfig.noCorsDomains.push(origin);
595
+ }
596
+ // Hold the timestamp of this request so we can decide when to
597
+ // send another request to this domain
598
+ requestConfig.crossOriginNoCorsDomains[origin.toLowerCase()] = Date.now();
599
+ // Remove the pending request from the cache
600
+ delete requestConfig.pendingNoCorsRequests[origin];
601
+ // Due to limitations of fetchMock at the version of the tooling
602
+ // in this project, we can't mock the response type of a no-cors request
603
+ // and thus we can't test this. So we are going to comment this out
604
+ // and leave it in place for now. If we need to test this, we can
605
+ // update the tooling to a version that supports this. Also
606
+ // JS SDK does not do this check, so we are going to leave it out for now.
607
+ // ================================================================
608
+ // no-cors requests are opaque to javascript
609
+ // and thus will always return a response with a type of "opaque"
610
+ // if (response.type === "opaque") {
611
+ // return Promise.resolve();
612
+ // } else {
613
+ // // Not sure if this is possible, but since we have a check above
614
+ // // lets handle the else case
615
+ // return Promise.reject(
616
+ // new Error(`no-cors request to ${origin} not opaque`)
617
+ // );
618
+ // }
619
+ // ================================================================
620
+ })
621
+ .catch((e) => {
622
+ // Not sure this is necessary, but if the request fails
623
+ // we should remove it from the pending requests
624
+ // and return a rejected promise with some information
625
+ delete requestConfig.pendingNoCorsRequests[origin];
626
+ return Promise.reject(new Error(`no-cors request to ${origin} failed`));
627
+ });
628
+ // return the promise
629
+ return requestConfig.pendingNoCorsRequests[origin];
630
+ }
631
+ /**
632
+ * Register the domains that are allowed to be used in no-cors requests
633
+ * This is called by `request` when the portal/self response is intercepted
634
+ * and the `.authorizedCrossOriginNoCorsDomains` property is set.
635
+ * @param authorizedCrossOriginNoCorsDomains
636
+ */
637
+ function registerNoCorsDomains(authorizedCrossOriginNoCorsDomains) {
638
+ // register the domains
639
+ authorizedCrossOriginNoCorsDomains.forEach((domain) => {
640
+ // ensure domain is lower case and ensure protocol is included
641
+ domain = domain.toLowerCase();
642
+ if (/^https?:\/\//.test(domain)) {
643
+ addNoCorsDomain(domain);
644
+ }
645
+ else {
646
+ // no protocol present, so add http and https
647
+ addNoCorsDomain("http://" + domain);
648
+ addNoCorsDomain("https://" + domain);
649
+ }
650
+ });
651
+ }
652
+ /**
653
+ * Ensure we don't get duplicate domains in the no-cors domains list
654
+ * @param domain
655
+ */
656
+ function addNoCorsDomain(url) {
657
+ // Since the caller of this always ensures a protocol is present
658
+ // we can safely use the URL constructor to get the origin
659
+ // and add it to the no-cors domains list
660
+ const uri = new URL(url);
661
+ const domain = uri.origin;
662
+ if (requestConfig.noCorsDomains.indexOf(domain) === -1) {
663
+ requestConfig.noCorsDomains.push(domain);
664
+ }
665
+ }
666
+ /**
667
+ * Is the origin of the passed url in the no-cors domains list?
668
+ * @param url
669
+ * @returns
670
+ */
671
+ function isNoCorsDomain(url) {
672
+ let result = false;
673
+ if (requestConfig.noCorsDomains.length) {
674
+ // is the current url in the no-cors domains?
675
+ const origin = new URL(url).origin.toLowerCase();
676
+ result = requestConfig.noCorsDomains.some((domain) => {
677
+ return origin.includes(domain);
678
+ });
679
+ }
680
+ return result;
681
+ }
682
+ /**
683
+ * Is the origin of the passed url in the no-cors domains list
684
+ * and do we need to send a no-cors request?
685
+ *
686
+ * @param url
687
+ * @returns
688
+ */
689
+ function isNoCorsRequestRequired(url) {
690
+ let result = false;
691
+ // is the current origin in the no-cors domains?
692
+ if (isNoCorsDomain(url)) {
693
+ const origin = new URL(url).origin.toLowerCase();
694
+ // check if we have sent a no-cors request to this domain in the last hour
695
+ const lastRequest = requestConfig.crossOriginNoCorsDomains[origin] || 0;
696
+ if (Date.now() - 60 * 60000 > lastRequest) {
697
+ result = true;
698
+ }
699
+ }
700
+ return result;
701
+ }
702
+
541
703
  /* Copyright (c) 2017-2018 Environmental Systems Research Institute, Inc.
542
704
  * Apache-2.0 */
543
705
  /**
@@ -558,6 +720,28 @@
558
720
  });
559
721
  }
560
722
 
723
+ /**
724
+ * Is the given URL the same origin as the current window?
725
+ * Used to determine if we need to do any additional cross-origin
726
+ * handling for the request.
727
+ * @param url
728
+ * @param win - optional window object to use for origin comparison
729
+ * (useful for testing)
730
+ * @returns
731
+ */
732
+ function isSameOrigin(url, win) {
733
+ var _a;
734
+ /* istanbul ignore next */
735
+ if ((!win && !window) || !url) {
736
+ return false;
737
+ }
738
+ else {
739
+ win = win || window;
740
+ const origin = (_a = win.location) === null || _a === void 0 ? void 0 : _a.origin;
741
+ return url.startsWith(origin);
742
+ }
743
+ }
744
+
561
745
  /* Copyright (c) 2017-2018 Environmental Systems Research Institute, Inc.
562
746
  * Apache-2.0 */
563
747
  const NODEJS_DEFAULT_REFERER_HEADER = `@esri/arcgis-rest-js`;
@@ -713,6 +897,10 @@
713
897
  needed to support sending IWA cookies */
714
898
  credentials: options.credentials || "same-origin"
715
899
  };
900
+ // Is this a no-cors domain? if so we need to set credentials to include
901
+ if (isNoCorsDomain(url)) {
902
+ fetchOptions.credentials = "include";
903
+ }
716
904
  // the /oauth2/platformSelf route will add X-Esri-Auth-Client-Id header
717
905
  // and that request needs to send cookies cross domain
718
906
  // so we need to set the credentials to "include"
@@ -748,7 +936,31 @@
748
936
  // for errors in GET requests we want the URL passed to the error to be the URL before
749
937
  // query params are applied.
750
938
  const originalUrl = url;
751
- return (authentication
939
+ // default to false, for nodejs
940
+ let sameOrigin = false;
941
+ // if we are in a browser, check if the url is same origin
942
+ /* istanbul ignore else */
943
+ if (typeof window !== "undefined") {
944
+ sameOrigin = isSameOrigin(url);
945
+ }
946
+ const requiresNoCors = !sameOrigin && isNoCorsRequestRequired(url);
947
+ // the /oauth2/platformSelf route will add X-Esri-Auth-Client-Id header
948
+ // and that request needs to send cookies cross domain
949
+ // so we need to set the credentials to "include"
950
+ if (options.headers &&
951
+ options.headers["X-Esri-Auth-Client-Id"] &&
952
+ url.indexOf("/oauth2/platformSelf") > -1) {
953
+ fetchOptions.credentials = "include";
954
+ }
955
+ // Simple first promise that we may turn into the no-cors request
956
+ let firstPromise = Promise.resolve();
957
+ if (requiresNoCors) {
958
+ // ensure we send cookies on the request after
959
+ fetchOptions.credentials = "include";
960
+ firstPromise = sendNoCorsRequest(url);
961
+ }
962
+ return firstPromise
963
+ .then(() => authentication
752
964
  ? authentication.getToken(url).catch((err) => {
753
965
  /**
754
966
  * append original request url and requestOptions
@@ -791,7 +1003,8 @@
791
1003
  const urlWithQueryString = queryParams === "" ? url : url + "?" + encodeQueryString(params);
792
1004
  if (
793
1005
  // This would exceed the maximum length for URLs by 2000 as default or as specified by the consumer and requires POST
794
- (options.maxUrlLength && urlWithQueryString.length > options.maxUrlLength) ||
1006
+ (options.maxUrlLength &&
1007
+ urlWithQueryString.length > options.maxUrlLength) ||
795
1008
  (!options.maxUrlLength && urlWithQueryString.length > 2000) ||
796
1009
  // Or if the customer requires the token to be hidden and it has not already been hidden in the header (for browsers)
797
1010
  (params.token && options.hideToken)) {
@@ -889,6 +1102,14 @@
889
1102
  // Most ArcGIS Server services will return a successful status code but include an error in the response body.
890
1103
  if ((params.f === "json" || params.f === "geojson") && !rawResponse) {
891
1104
  const response = checkForErrors(data, originalUrl, params, options, originalAuthError);
1105
+ // If this was a portal/self call, and we got authorizedNoCorsDomains back
1106
+ // register them
1107
+ if (data && /\/sharing\/rest\/(accounts|portals)\/self/i.test(url)) {
1108
+ // if we have a list of no-cors domains, register them
1109
+ if (Array.isArray(data.authorizedCrossOriginNoCorsDomains)) {
1110
+ registerNoCorsDomains(data.authorizedCrossOriginNoCorsDomains);
1111
+ }
1112
+ }
892
1113
  if (originalAuthError) {
893
1114
  /* If the request was made to an unfederated service that
894
1115
  didn't require authentication, add the base url and a dummy token