@rabbitio/ui-kit 1.0.0-beta.41 → 1.0.0-beta.43

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.
Files changed (112) hide show
  1. package/.gitlab-ci.yml +29 -0
  2. package/.husky/commit-msg +8 -0
  3. package/.husky/pre-push +1 -0
  4. package/README.md +13 -4
  5. package/coverage/base.css +224 -0
  6. package/coverage/block-navigation.js +87 -0
  7. package/coverage/clover.xml +6516 -0
  8. package/coverage/coverage-final.json +43 -0
  9. package/coverage/favicon.png +0 -0
  10. package/coverage/index.html +416 -0
  11. package/coverage/prettify.css +1 -0
  12. package/coverage/prettify.js +2 -0
  13. package/coverage/rabbit-ui-kit/index.html +116 -0
  14. package/coverage/rabbit-ui-kit/index.js.html +88 -0
  15. package/coverage/rabbit-ui-kit/src/common/adapters/axiosAdapter.js.html +190 -0
  16. package/coverage/rabbit-ui-kit/src/common/adapters/index.html +116 -0
  17. package/coverage/rabbit-ui-kit/src/common/amountUtils.js.html +1393 -0
  18. package/coverage/rabbit-ui-kit/src/common/errorUtils.js.html +211 -0
  19. package/coverage/rabbit-ui-kit/src/common/external-apis/apiGroups.js.html +250 -0
  20. package/coverage/rabbit-ui-kit/src/common/external-apis/index.html +131 -0
  21. package/coverage/rabbit-ui-kit/src/common/external-apis/ipAddressProviders.js.html +499 -0
  22. package/coverage/rabbit-ui-kit/src/common/fiatCurrenciesService.js.html +568 -0
  23. package/coverage/rabbit-ui-kit/src/common/index.html +146 -0
  24. package/coverage/rabbit-ui-kit/src/common/models/blockchain.js.html +115 -0
  25. package/coverage/rabbit-ui-kit/src/common/models/coin.js.html +556 -0
  26. package/coverage/rabbit-ui-kit/src/common/models/index.html +146 -0
  27. package/coverage/rabbit-ui-kit/src/common/models/protocol.js.html +100 -0
  28. package/coverage/rabbit-ui-kit/src/common/utils/cache.js.html +889 -0
  29. package/coverage/rabbit-ui-kit/src/common/utils/emailAPI.js.html +139 -0
  30. package/coverage/rabbit-ui-kit/src/common/utils/index.html +161 -0
  31. package/coverage/rabbit-ui-kit/src/common/utils/logging/index.html +131 -0
  32. package/coverage/rabbit-ui-kit/src/common/utils/logging/logger.js.html +229 -0
  33. package/coverage/rabbit-ui-kit/src/common/utils/logging/logsStorage.js.html +268 -0
  34. package/coverage/rabbit-ui-kit/src/common/utils/postponeExecution.js.html +118 -0
  35. package/coverage/rabbit-ui-kit/src/common/utils/safeStringify.js.html +235 -0
  36. package/coverage/rabbit-ui-kit/src/components/atoms/AssetIcon/AssetIcon.jsx.html +250 -0
  37. package/coverage/rabbit-ui-kit/src/components/atoms/AssetIcon/index.html +116 -0
  38. package/coverage/rabbit-ui-kit/src/components/atoms/LoadingDots/LoadingDots.jsx.html +256 -0
  39. package/coverage/rabbit-ui-kit/src/components/atoms/LoadingDots/index.html +116 -0
  40. package/coverage/rabbit-ui-kit/src/components/atoms/SupportChat/SupportChat.jsx.html +229 -0
  41. package/coverage/rabbit-ui-kit/src/components/atoms/SupportChat/index.html +116 -0
  42. package/coverage/rabbit-ui-kit/src/components/atoms/buttons/Button/Button.jsx.html +802 -0
  43. package/coverage/rabbit-ui-kit/src/components/atoms/buttons/Button/index.html +116 -0
  44. package/coverage/rabbit-ui-kit/src/components/hooks/index.html +131 -0
  45. package/coverage/rabbit-ui-kit/src/components/hooks/useCallHandlingErrors.js.html +163 -0
  46. package/coverage/rabbit-ui-kit/src/components/hooks/useReferredState.js.html +157 -0
  47. package/coverage/rabbit-ui-kit/src/components/utils/index.html +146 -0
  48. package/coverage/rabbit-ui-kit/src/components/utils/inputValueProviders.js.html +259 -0
  49. package/coverage/rabbit-ui-kit/src/components/utils/uiUtils.js.html +127 -0
  50. package/coverage/rabbit-ui-kit/src/components/utils/urlQueryUtils.js.html +346 -0
  51. package/coverage/rabbit-ui-kit/src/index.html +116 -0
  52. package/coverage/rabbit-ui-kit/src/index.js.html +250 -0
  53. package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/cacheAndConcurrentRequestsResolver.js.html +1762 -0
  54. package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/cachedRobustExternalApiCallerService.js.html +649 -0
  55. package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/cancelProcessing.js.html +172 -0
  56. package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/concurrentCalculationsMetadataHolder.js.html +394 -0
  57. package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/externalApiProvider.js.html +553 -0
  58. package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/externalServicesStatsCollector.js.html +331 -0
  59. package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/index.html +206 -0
  60. package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/robustExternalAPICallerService.js.html +1249 -0
  61. package/coverage/rabbit-ui-kit/src/swaps-lib/external-apis/index.html +131 -0
  62. package/coverage/rabbit-ui-kit/src/swaps-lib/external-apis/swapProvider.js.html +727 -0
  63. package/coverage/rabbit-ui-kit/src/swaps-lib/external-apis/swapspaceSwapProvider.js.html +2899 -0
  64. package/coverage/rabbit-ui-kit/src/swaps-lib/models/baseSwapCreationInfo.js.html +214 -0
  65. package/coverage/rabbit-ui-kit/src/swaps-lib/models/existingSwap.js.html +304 -0
  66. package/coverage/rabbit-ui-kit/src/swaps-lib/models/existingSwapWithFiatData.js.html +487 -0
  67. package/coverage/rabbit-ui-kit/src/swaps-lib/models/index.html +146 -0
  68. package/coverage/rabbit-ui-kit/src/swaps-lib/services/index.html +116 -0
  69. package/coverage/rabbit-ui-kit/src/swaps-lib/services/publicSwapService.js.html +2191 -0
  70. package/coverage/rabbit-ui-kit/src/swaps-lib/utils/index.html +116 -0
  71. package/coverage/rabbit-ui-kit/src/swaps-lib/utils/swapUtils.js.html +742 -0
  72. package/coverage/rabbit-ui-kit/stories/atoms/LoadingDots.stories.jsx.html +226 -0
  73. package/coverage/rabbit-ui-kit/stories/atoms/buttons/Button.stories.jsx.html +946 -0
  74. package/coverage/rabbit-ui-kit/stories/atoms/buttons/index.html +116 -0
  75. package/coverage/rabbit-ui-kit/stories/atoms/index.html +116 -0
  76. package/coverage/sort-arrow-sprite.png +0 -0
  77. package/coverage/sorter.js +196 -0
  78. package/dist/index.cjs +1706 -1498
  79. package/dist/index.cjs.map +1 -1
  80. package/dist/index.modern.js +817 -666
  81. package/dist/index.modern.js.map +1 -1
  82. package/dist/index.module.js +1704 -1498
  83. package/dist/index.module.js.map +1 -1
  84. package/dist/index.umd.js +1664 -1456
  85. package/dist/index.umd.js.map +1 -1
  86. package/package.json +11 -3
  87. package/src/common/amountUtils.js +4 -2
  88. package/src/common/external-apis/ipAddressProviders.js +138 -0
  89. package/src/common/tests/integration/external-apis/ipAddressProviders/getClientIpAddress.test.js +14 -0
  90. package/src/common/utils/cache.js +4 -4
  91. package/src/components/tests/utils/inputValueProviders/provideFormatOfFloatValueByInputString.test.js +139 -0
  92. package/src/components/tests/utils/urlQueryUtils/getQueryParameterValues.test.js +71 -0
  93. package/src/components/tests/utils/urlQueryUtils/saveQueryParameterAndValues.test.js +144 -0
  94. package/src/components/utils/inputValueProviders.js +58 -0
  95. package/src/index.js +2 -0
  96. package/src/robustExteranlApiCallerService/robustExternalAPICallerService.js +4 -2
  97. package/src/robustExteranlApiCallerService/tests/robustExternalAPICallerService/robustExternalAPICallerService/callExternalAPI/_performCallAttempt.test.js +787 -0
  98. package/src/robustExteranlApiCallerService/tests/robustExternalAPICallerService/robustExternalAPICallerService/callExternalAPI/callExternalAPI.test.js +745 -0
  99. package/src/robustExteranlApiCallerService/tests/robustExternalAPICallerService/robustExternalAPICallerService/constructor.test.js +31 -0
  100. package/src/swaps-lib/external-apis/swapProvider.js +17 -4
  101. package/src/swaps-lib/external-apis/swapspaceSwapProvider.js +91 -30
  102. package/src/swaps-lib/models/baseSwapCreationInfo.js +4 -1
  103. package/src/swaps-lib/models/existingSwap.js +3 -0
  104. package/src/swaps-lib/models/existingSwapWithFiatData.js +4 -0
  105. package/src/swaps-lib/services/publicSwapService.js +32 -4
  106. package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/_fetchSupportedCurrenciesIfNeeded.test.js +506 -0
  107. package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/createSwap.test.js +1311 -0
  108. package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/getAllSupportedCurrencies.test.js +76 -0
  109. package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/getDepositCurrencies.test.js +82 -0
  110. package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/getSwapInfo.test.js +1892 -0
  111. package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/getWithdrawalCurrencies.test.js +111 -0
  112. package/src/swaps-lib/test/utils/swapUtils/safeHandleRequestsLimitExceeding.test.js +88 -0
@@ -1,9 +1,9 @@
1
1
  import React, { useState, useRef, useEffect, useCallback } from 'react';
2
2
  import { BigNumber } from 'bignumber.js';
3
3
  import axios from 'axios';
4
- import { v4 } from 'uuid';
5
- import Hashes from 'jshashes';
6
4
  import EventBusInstance from 'eventbusjs';
5
+ import Hashes from 'jshashes';
6
+ import { v4 } from 'uuid';
7
7
 
8
8
  function createCommonjsModule(fn) {
9
9
  var module = { exports: {} };
@@ -1623,7 +1623,7 @@ var Logger = /*#__PURE__*/function () {
1623
1623
  return Logger;
1624
1624
  }();
1625
1625
 
1626
- function _catch$8(body, recover) {
1626
+ function _catch$9(body, recover) {
1627
1627
  try {
1628
1628
  var result = body();
1629
1629
  } catch (e) {
@@ -1639,7 +1639,7 @@ function useCallHandlingErrors() {
1639
1639
  setState = _useState[1];
1640
1640
  return useCallback(function (functionToBeCalled, event) {
1641
1641
  try {
1642
- var _temp = _catch$8(function () {
1642
+ var _temp = _catch$9(function () {
1643
1643
  return Promise.resolve(functionToBeCalled(event)).then(function () {});
1644
1644
  }, function (error) {
1645
1645
  Logger.logError(error, (functionToBeCalled == null ? void 0 : functionToBeCalled.name) || "errorBoundaryTrigger", "Caught by ErrorBoundary");
@@ -1692,6 +1692,60 @@ var handleClickOutside = function handleClickOutside(exceptionsRefs, callback) {
1692
1692
  };
1693
1693
  };
1694
1694
 
1695
+ var InputValuesProviders = /*#__PURE__*/function () {
1696
+ function InputValuesProviders() {}
1697
+ /**
1698
+ * Designed to be called onKeyUp event of html input field for float value
1699
+ * Removes all prohibited stuff from the given float string and remains only allowed.
1700
+ * Removes digits before and after the dot.
1701
+ *
1702
+ * @param inputString {string} string to be corrected
1703
+ * @param maxValue {string} max value for the correcting float value
1704
+ * @param digitsAfterDot {number} count of digits after the dot that this method should provide, min 0
1705
+ * @return {string} corrected float value string
1706
+ */
1707
+ InputValuesProviders.provideFormatOfFloatValueByInputString = function provideFormatOfFloatValueByInputString(inputString, digitsAfterDot, maxValue) {
1708
+ var _parts$2;
1709
+ if (digitsAfterDot === void 0) {
1710
+ digitsAfterDot = 2;
1711
+ }
1712
+ if (maxValue === void 0) {
1713
+ maxValue = null;
1714
+ }
1715
+ var value = inputString;
1716
+ if (!value) {
1717
+ return "";
1718
+ }
1719
+ if (digitsAfterDot < 0) {
1720
+ throw new Error("Min suffix length is 0, got " + digitsAfterDot);
1721
+ }
1722
+ value = value.replace(/[,]/g, "."); // replaces commas with dots
1723
+ value = value.replace(/[^0-9.]/g, ""); // remove non digits/dots
1724
+ value = value.replace(/^\./g, "0."); // adds leading zero
1725
+ value = value.replace(/\.+/g, "."); // replaces series of dots with single dot
1726
+
1727
+ var parts = value.split(".");
1728
+ if (parts.length > 2) {
1729
+ // removes all after second dot and itself
1730
+ parts = [parts[0], parts[1]];
1731
+ }
1732
+ if (maxValue != null) {
1733
+ var _parts$;
1734
+ var maxDigitsCountBeforeTheDot = BigNumber(maxValue).toFixed(0).length;
1735
+ if (((_parts$ = parts[0]) == null ? void 0 : _parts$.length) > maxDigitsCountBeforeTheDot) {
1736
+ // removes redundant prefix digits
1737
+ parts[0] = parts[0].substring(parts[0].length - maxDigitsCountBeforeTheDot, parts[0].length);
1738
+ }
1739
+ }
1740
+ if (((_parts$2 = parts[1]) == null ? void 0 : _parts$2.length) > digitsAfterDot) {
1741
+ // removes redundant suffix digits
1742
+ parts[1] = parts[1].substring(0, digitsAfterDot);
1743
+ }
1744
+ return parts.join(".");
1745
+ };
1746
+ return InputValuesProviders;
1747
+ }();
1748
+
1695
1749
  var PARAMETER_VALUES_SEPARATOR = "|*|"; // Sting that with high probability will not be in the user's data
1696
1750
 
1697
1751
  /**
@@ -2092,11 +2146,13 @@ var AmountUtils = /*#__PURE__*/function () {
2092
2146
  }
2093
2147
 
2094
2148
  /**
2149
+ * Returns integer part of number as a string.
2150
+ *
2095
2151
  * @param amount {BigNumber|number|string|null|undefined} The number value to be trimmed.
2096
2152
  * HEX strings also allowed "0x..." and JS hex numbers
2097
2153
  * @return {string|null}
2098
2154
  */;
2099
- AmountUtils.intStr = function intStr(amount) {
2155
+ AmountUtils.toIntegerString = function toIntegerString(amount) {
2100
2156
  return this.trim(amount, 0);
2101
2157
  }
2102
2158
 
@@ -2183,7 +2239,7 @@ var AmountUtils = /*#__PURE__*/function () {
2183
2239
  leftNumber = leftNumber.times(multiplier);
2184
2240
  }
2185
2241
  }
2186
- var leftAmountString = AmountUtils.intStr(leftNumber);
2242
+ var leftAmountString = AmountUtils.toIntegerString(leftNumber);
2187
2243
  var rightAmountString = right != null ? right.toFixed(rightCurrencyDigitsAfterDots, BigNumber.ROUND_FLOOR) : null;
2188
2244
  return leftAmountString + " " + leftTicker + " ~ " + (rightAmountString != null ? rightAmountString : "?") + " " + rightTicker;
2189
2245
  } catch (e) {
@@ -2419,10 +2475,13 @@ var Coin = /*#__PURE__*/function () {
2419
2475
  */
2420
2476
  var Cache = /*#__PURE__*/function () {
2421
2477
  /**
2422
- * @param eventBus {EventBus} EventBus.js lib instance
2423
- * @param [noSessionEvents=[]] {string[]} array of events that will be treated as "no session"
2478
+ * @param [eventBus=null] {EventBus} EventBus.js lib instance if you plan to use Cache with events handling
2479
+ * @param [noSessionEvents=[]] {string[]} array of events that will be treated as "no session", you should pass EventBus to make it work
2424
2480
  */
2425
2481
  function Cache(eventBus, noSessionEvents) {
2482
+ if (eventBus === void 0) {
2483
+ eventBus = null;
2484
+ }
2426
2485
  if (noSessionEvents === void 0) {
2427
2486
  noSessionEvents = [];
2428
2487
  }
@@ -2518,7 +2577,7 @@ var Cache = /*#__PURE__*/function () {
2518
2577
  });
2519
2578
  if (eventAndKeys) {
2520
2579
  eventAndKeys.push(key);
2521
- } else {
2580
+ } else if (_this._eventBus) {
2522
2581
  _this._eventDependentDataKeys.push([event, key]);
2523
2582
  _this._eventBus.addEventListener(event, function () {
2524
2583
  try {
@@ -2677,7 +2736,7 @@ var Cache = /*#__PURE__*/function () {
2677
2736
  return Cache;
2678
2737
  }();
2679
2738
 
2680
- function _catch$7(body, recover) {
2739
+ function _catch$8(body, recover) {
2681
2740
  try {
2682
2741
  var result = body();
2683
2742
  } catch (e) {
@@ -2695,7 +2754,7 @@ function postponeExecution(execution, timeoutMS) {
2695
2754
  return new Promise(function (resolve, reject) {
2696
2755
  setTimeout(function () {
2697
2756
  try {
2698
- var _temp = _catch$7(function () {
2757
+ var _temp = _catch$8(function () {
2699
2758
  return Promise.resolve(execution()).then(function (_execution) {
2700
2759
  resolve(_execution);
2701
2760
  });
@@ -2779,7 +2838,7 @@ var AxiosAdapter = /*#__PURE__*/function () {
2779
2838
  return AxiosAdapter;
2780
2839
  }();
2781
2840
 
2782
- function _catch$6(body, recover) {
2841
+ function _catch$7(body, recover) {
2783
2842
  try {
2784
2843
  var result = body();
2785
2844
  } catch (e) {
@@ -2795,7 +2854,7 @@ var EmailsApi = /*#__PURE__*/function () {
2795
2854
  EmailsApi.sendEmail = function sendEmail(subject, body) {
2796
2855
  try {
2797
2856
  var _this = this;
2798
- var _temp = _catch$6(function () {
2857
+ var _temp = _catch$7(function () {
2799
2858
  var url = window.location.protocol + "//" + window.location.host + "/api/v1/" + _this.serverEndpointEntity;
2800
2859
  return Promise.resolve(axios.post(url, {
2801
2860
  subject: subject,
@@ -2813,736 +2872,281 @@ var EmailsApi = /*#__PURE__*/function () {
2813
2872
  }();
2814
2873
  EmailsApi.serverEndpointEntity = "emails";
2815
2874
 
2816
- /**
2817
- * This util helps to avoid duplicated calls to a shared resource.
2818
- * It tracks is there currently active calculation for the specific cache id and make all other requests
2819
- * with the same cache id waiting for this active calculation to be finished. When the calculation ends
2820
- * the resolver allows all the waiting requesters to get the data from cache and start their own calculations.
2821
- *
2822
- * This class should be instantiated inside some other service where you need to request some resource concurrently.
2823
- * Rules:
2824
- * 1. When you need to make a request inside your main service call 'getCachedOrWaitForCachedOrAcquireLock'
2825
- * on the instance of this class and await for the result. If the flag allowing to start calculation is true
2826
- * then you can request data inside your main service. Otherwise you should use the cached data as an another
2827
- * requester just finished the most resent requesting and there is actual data in the cache that
2828
- * is returned to you here.
2829
- * 1.1 Also you can acquire a lock directly if you don't want to get cached data. Use the corresponding method 'acquireLock'.
2830
- *
2831
- * 2. If you start requesting (when you successfully acquired the lock) then after receiving the result of your
2832
- * requesting you should call the 'saveCachedData' so the retrieved data will appear in the cache.
2833
- *
2834
- * 3. If you successfully acquired the lock then you should after calling the 'saveCachedData' call
2835
- * the 'releaseLock' - this is mandatory to release the lock and allow other requesters to perform their requests.
2836
- * WARNING: If for any reason you forget to call this method then this class instance will wait perpetually for
2837
- * the lock releasing and all your attempts to request the data will constantly fail. So usually call it
2838
- * inside the 'finally' block.
2839
- *
2840
- * TODO: [tests, critical++] add unit tests - massively used logic and can produce sophisticated concurrency bugs
2841
- */
2842
-
2843
- function _settle$2(pact, state, value) {
2844
- if (!pact.s) {
2845
- if (value instanceof _Pact$2) {
2846
- if (value.s) {
2847
- if (state & 1) {
2848
- state = value.s;
2849
- }
2850
- value = value.v;
2851
- } else {
2852
- value.o = _settle$2.bind(null, pact, state);
2853
- return;
2854
- }
2855
- }
2856
- if (value && value.then) {
2857
- value.then(_settle$2.bind(null, pact, state), _settle$2.bind(null, pact, 2));
2858
- return;
2875
+ var ExternalApiProvider = /*#__PURE__*/function () {
2876
+ /**
2877
+ * Creates an instance of external api provider.
2878
+ *
2879
+ * If you need sub-request then use 'subRequestIndex' to check current request index in functions below.
2880
+ * Also use array for 'httpMethod'.
2881
+ *
2882
+ * If the endpoint of dedicated provider has pagination then you should customize the behavior using
2883
+ * "changeQueryParametersForPageNumber", "checkWhetherResponseIsForLastPage".
2884
+ *
2885
+ * We perform RPS counting all over the App to avoid blocking our clients due to abuses of the providers.
2886
+ *
2887
+ * @param endpoint {string} URL to the provider's endpoint. Note: you can customize it using composeQueryString
2888
+ * @param [httpMethod] {string|string[]} one of "get", "post", "put", "patch", "delete" or an array of these values
2889
+ * for request having sub-requests
2890
+ * @param [timeout] {number} number of milliseconds to wait for the response
2891
+ * @param [apiGroup] {ApiGroup} singleton object containing parameters of API group. Helpful when you use the same
2892
+ * api for different providers to avoid hardcoding RPS inside each provider what can cause mistakes
2893
+ * @param [specificHeaders] {Object} contains specific keys (headers) and values (their content) if needed for this provider
2894
+ * @param [maxPageLength] {number} optional number of items per page if the request supports pagination
2895
+ */
2896
+ function ExternalApiProvider(endpoint, httpMethod, timeout, apiGroup, specificHeaders, maxPageLength) {
2897
+ var _maxPageLength, _specificHeaders;
2898
+ if (specificHeaders === void 0) {
2899
+ specificHeaders = {};
2859
2900
  }
2860
- pact.s = state;
2861
- pact.v = value;
2862
- const observer = pact.o;
2863
- if (observer) {
2864
- observer(pact);
2901
+ if (maxPageLength === void 0) {
2902
+ maxPageLength = Number.MAX_SAFE_INTEGER;
2865
2903
  }
2904
+ this.endpoint = endpoint;
2905
+ this.httpMethod = httpMethod != null ? httpMethod : "get";
2906
+ // TODO: [refactoring, critical] We have two timeouts for robust data retrieval - here and inside the service method call, need to remain the only
2907
+ this.timeout = timeout != null ? timeout : 10000;
2908
+ // TODO: [refactoring, critical] We need single place for all RPSes as we use them as hardcoded constants now inside different services
2909
+ this.apiGroup = apiGroup;
2910
+ this.maxPageLength = (_maxPageLength = maxPageLength) != null ? _maxPageLength : Number.MAX_SAFE_INTEGER;
2911
+ this.niceFactor = 1;
2912
+ this.specificHeaders = (_specificHeaders = specificHeaders) != null ? _specificHeaders : {};
2866
2913
  }
2867
- }
2868
-
2869
- /**
2870
- * Util class to control access to a resource when it can be called in parallel for the same result.
2871
- * (E.g. getting today coins-fiat rates from some API).
2872
- */
2873
- var _Pact$2 = /*#__PURE__*/function () {
2874
- function _Pact() {}
2875
- _Pact.prototype.then = function (onFulfilled, onRejected) {
2876
- var result = new _Pact();
2877
- var state = this.s;
2878
- if (state) {
2879
- var callback = state & 1 ? onFulfilled : onRejected;
2880
- if (callback) {
2881
- try {
2882
- _settle$2(result, 1, callback(this.v));
2883
- } catch (e) {
2884
- _settle$2(result, 2, e);
2885
- }
2886
- return result;
2887
- } else {
2888
- return this;
2889
- }
2890
- }
2891
- this.o = function (_this) {
2892
- try {
2893
- var value = _this.v;
2894
- if (_this.s & 1) {
2895
- _settle$2(result, 1, onFulfilled ? onFulfilled(value) : value);
2896
- } else if (onRejected) {
2897
- _settle$2(result, 1, onRejected(value));
2898
- } else {
2899
- _settle$2(result, 2, value);
2900
- }
2901
- } catch (e) {
2902
- _settle$2(result, 2, e);
2903
- }
2904
- };
2905
- return result;
2914
+ var _proto = ExternalApiProvider.prototype;
2915
+ _proto.getRps = function getRps() {
2916
+ var _this$apiGroup$rps;
2917
+ return (_this$apiGroup$rps = this.apiGroup.rps) != null ? _this$apiGroup$rps : 2;
2906
2918
  };
2907
- return _Pact;
2908
- }();
2909
- function _isSettledPact$2(thenable) {
2910
- return thenable instanceof _Pact$2 && thenable.s & 1;
2911
- }
2912
- function _for$1(test, update, body) {
2913
- var stage;
2914
- for (;;) {
2915
- var shouldContinue = test();
2916
- if (_isSettledPact$2(shouldContinue)) {
2917
- shouldContinue = shouldContinue.v;
2918
- }
2919
- if (!shouldContinue) {
2920
- return result;
2921
- }
2922
- if (shouldContinue.then) {
2923
- stage = 0;
2924
- break;
2925
- }
2926
- var result = body();
2927
- if (result && result.then) {
2928
- if (_isSettledPact$2(result)) {
2929
- result = result.s;
2930
- } else {
2931
- stage = 1;
2932
- break;
2933
- }
2934
- }
2935
- if (update) {
2936
- var updateValue = update();
2937
- if (updateValue && updateValue.then && !_isSettledPact$2(updateValue)) {
2938
- stage = 2;
2939
- break;
2940
- }
2941
- }
2919
+ _proto.isRpsExceeded = function isRpsExceeded() {
2920
+ return this.apiGroup.isRpsExceeded();
2921
+ };
2922
+ _proto.actualizeLastCalledTimestamp = function actualizeLastCalledTimestamp() {
2923
+ this.apiGroup.actualizeLastCalledTimestamp();
2924
+ };
2925
+ _proto.getApiGroupId = function getApiGroupId() {
2926
+ return this.apiGroup.id;
2942
2927
  }
2943
- var pact = new _Pact$2();
2944
- var reject = _settle$2.bind(null, pact, 2);
2945
- (stage === 0 ? shouldContinue.then(_resumeAfterTest) : stage === 1 ? result.then(_resumeAfterBody) : updateValue.then(_resumeAfterUpdate)).then(void 0, reject);
2946
- return pact;
2947
- function _resumeAfterBody(value) {
2948
- result = value;
2949
- do {
2950
- if (update) {
2951
- updateValue = update();
2952
- if (updateValue && updateValue.then && !_isSettledPact$2(updateValue)) {
2953
- updateValue.then(_resumeAfterUpdate).then(void 0, reject);
2954
- return;
2955
- }
2956
- }
2957
- shouldContinue = test();
2958
- if (!shouldContinue || _isSettledPact$2(shouldContinue) && !shouldContinue.v) {
2959
- _settle$2(pact, 1, result);
2960
- return;
2961
- }
2962
- if (shouldContinue.then) {
2963
- shouldContinue.then(_resumeAfterTest).then(void 0, reject);
2964
- return;
2965
- }
2966
- result = body();
2967
- if (_isSettledPact$2(result)) {
2968
- result = result.v;
2969
- }
2970
- } while (!result || !result.then);
2971
- result.then(_resumeAfterBody).then(void 0, reject);
2928
+
2929
+ /**
2930
+ * Some endpoint can require several sub requests. Example is one request to get confirmed transactions
2931
+ * and another request for unconfirmed transactions. You should override this method to return true for such requests.
2932
+ *
2933
+ * @return {boolean} true if this provider requires several requests to retrieve the data
2934
+ */;
2935
+ _proto.doesRequireSubRequests = function doesRequireSubRequests() {
2936
+ return false;
2972
2937
  }
2973
- function _resumeAfterTest(shouldContinue) {
2974
- if (shouldContinue) {
2975
- result = body();
2976
- if (result && result.then) {
2977
- result.then(_resumeAfterBody).then(void 0, reject);
2978
- } else {
2979
- _resumeAfterBody(result);
2980
- }
2981
- } else {
2982
- _settle$2(pact, 1, result);
2983
- }
2984
- }
2985
- function _resumeAfterUpdate() {
2986
- if (shouldContinue = test()) {
2987
- if (shouldContinue.then) {
2988
- shouldContinue.then(_resumeAfterTest).then(void 0, reject);
2989
- } else {
2990
- _resumeAfterTest(shouldContinue);
2991
- }
2992
- } else {
2993
- _settle$2(pact, 1, result);
2994
- }
2995
- }
2996
- }
2997
- function _catch$5(body, recover) {
2998
- try {
2999
- var result = body();
3000
- } catch (e) {
3001
- return recover(e);
3002
- }
3003
- if (result && result.then) {
3004
- return result.then(void 0, recover);
3005
- }
3006
- return result;
3007
- }
3008
- var CacheAndConcurrentRequestsResolver = /*#__PURE__*/function () {
3009
- /**
3010
- * @param bio {string} unique identifier for the exact service
3011
- * @param cache {Cache} cache
3012
- * @param cacheTtl {number|null} time to live for cache ms. 0 or null means the cache cannot expire
3013
- * @param [maxCallAttemptsToWaitForAlreadyRunningRequest=100] {number} number of request allowed to do waiting for
3014
- * result before we fail the original request. Use custom value only if you need to make the attempts count
3015
- * and polling interval changes.
3016
- * @param [timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished=1000] {number}
3017
- * timeout ms for polling for a result. if you change maxCallAttemptsToWaitForAlreadyRunningRequest
3018
- * then this parameter maybe also require the custom value.
3019
- * @param [removeExpiredCacheAutomatically=true] {boolean}
3020
- */
3021
- function CacheAndConcurrentRequestsResolver(bio, cache, cacheTtl, removeExpiredCacheAutomatically, maxCallAttemptsToWaitForAlreadyRunningRequest, timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished) {
3022
- if (removeExpiredCacheAutomatically === void 0) {
3023
- removeExpiredCacheAutomatically = true;
3024
- }
3025
- if (maxCallAttemptsToWaitForAlreadyRunningRequest === void 0) {
3026
- maxCallAttemptsToWaitForAlreadyRunningRequest = 100;
3027
- }
3028
- if (timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished === void 0) {
3029
- timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished = 1000;
3030
- }
3031
- if (cacheTtl != null && cacheTtl < timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished * 2) {
3032
- /*
3033
- * During the lifetime of this service e.g. if the data is being retrieved slowly we can get
3034
- * RACE CONDITION when we constantly retrieve data and during retrieval it is expired, so we are trying
3035
- * to retrieve it again and again.
3036
- * We have a protection mechanism that we will wait no more than
3037
- * maxCallAttemptsToWaitForAlreadyRunningRequest * timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished
3038
- * but this additional check is aimed to reduce potential loading time for some requests.
3039
- */
3040
- throw new Error("DEV: Wrong parameters passed to construct " + bio + " - TTL " + cacheTtl + " should be 2 times greater than " + timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished);
3041
- }
3042
- this._bio = bio;
3043
- this._cache = cache;
3044
- this._cacheTtlMs = cacheTtl != null ? cacheTtl : null;
3045
- this._maxExecutionTimeMs = maxCallAttemptsToWaitForAlreadyRunningRequest * timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished;
3046
- this._removeExpiredCacheAutomatically = removeExpiredCacheAutomatically;
3047
- this._requestsManager = new ManagerOfRequestsToTheSameResource(bio, maxCallAttemptsToWaitForAlreadyRunningRequest, timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished);
2938
+
2939
+ /**
2940
+ * Some endpoint support pagination. Override this method if so and implement corresponding methods.
2941
+ *
2942
+ * @return {boolean} true if this provider requires several requests to retrieve the data
2943
+ */;
2944
+ _proto.doesSupportPagination = function doesSupportPagination() {
2945
+ return false;
3048
2946
  }
3049
2947
 
3050
2948
  /**
3051
- * When using this service this is the major method you should call to get data by cache id.
3052
- * This method checks is there cached data and ether
3053
- * - returns you flag that you can start requesting data from the shared resource
3054
- * - or if there is already started calculation waits until it is finished (removed from this service)
3055
- * and returns you the retrieved data
3056
- * - or just returns you the cached data
3057
- *
3058
- * 'canStartDataRetrieval' equal true means that the lock was acquired, and you should manually call 'saveCachedData'
3059
- * if needed and then 'releaseLock' to mark this calculation as finished so other
3060
- * requesters can take their share of the resource.
3061
- *
3062
- * @param cacheId {string}
3063
- * @return {Promise<({
3064
- * canStartDataRetrieval: true,
3065
- * cachedData: any,
3066
- * lockId: string
3067
- * }|{
3068
- * canStartDataRetrieval: false,
3069
- * cachedData: any
3070
- * })>}
3071
- */
3072
- var _proto = CacheAndConcurrentRequestsResolver.prototype;
3073
- _proto.getCachedOrWaitForCachedOrAcquireLock = function getCachedOrWaitForCachedOrAcquireLock(cacheId) {
3074
- try {
3075
- var _this = this;
3076
- return Promise.resolve(_catch$5(function () {
3077
- function _temp2() {
3078
- var _cached, _cached2;
3079
- return calculationId ? {
3080
- canStartDataRetrieval: true,
3081
- cachedData: (_cached = cached) != null ? _cached : cachedDataBackupIsPresentButExpired,
3082
- lockId: calculationId
3083
- } : {
3084
- canStartDataRetrieval: false,
3085
- cachedData: (_cached2 = cached) != null ? _cached2 : cachedDataBackupIsPresentButExpired
3086
- };
3087
- }
3088
- var startedAtTimestamp = Date.now();
3089
- var cached = _this._cache.get(cacheId);
3090
- var cachedDataBackupIsPresentButExpired = null;
3091
- if (cached != null && !_this._removeExpiredCacheAutomatically) {
3092
- var lastUpdateTimestamp = _this._cache.getLastUpdateTimestamp(cacheId);
3093
- if ((lastUpdateTimestamp != null ? lastUpdateTimestamp : 0) + _this._cacheTtlMs < Date.now()) {
3094
- /*
3095
- * Here we are manually clearing 'cached' value retrieved from cache to force the data loading.
3096
- * But we save its value first to the backup variable to be able to return this value if ongoing
3097
- * requesting fails.
3098
- */
3099
- cachedDataBackupIsPresentButExpired = cached;
3100
- cached = null;
3101
- }
3102
- }
3103
- var calculationId = null;
3104
- var isRetrievedCacheExpired = true;
3105
- var isWaitingForActiveCalculationSucceeded;
3106
- var weStillHaveSomeTimeToProceedExecution = true;
3107
- var _temp = _for$1(function () {
3108
- return calculationId == null && cached == null && !!isRetrievedCacheExpired && !!weStillHaveSomeTimeToProceedExecution;
3109
- }, void 0, function () {
3110
- return Promise.resolve(_this._requestsManager.startCalculationOrWaitForActiveToFinish(cacheId)).then(function (result) {
3111
- calculationId = typeof result === "string" ? result : null;
3112
- isWaitingForActiveCalculationSucceeded = typeof result === "boolean" ? result : null;
3113
- cached = _this._cache.get(cacheId);
3114
- isRetrievedCacheExpired = isWaitingForActiveCalculationSucceeded && cached == null;
3115
- weStillHaveSomeTimeToProceedExecution = Date.now() - startedAtTimestamp < _this._maxExecutionTimeMs;
3116
- });
3117
- });
3118
- return _temp && _temp.then ? _temp.then(_temp2) : _temp2(_temp);
3119
- }, function (e) {
3120
- improveAndRethrow(e, _this._bio + ".getCachedOrWaitForCachedOrAcquireLock");
3121
- }));
3122
- } catch (e) {
3123
- return Promise.reject(e);
3124
- }
3125
- }
3126
- /**
3127
- * Returns just the current cache value for the given id.
3128
- * Doesn't wait for the active calculation, doesn't acquire lock, just retrieves the current cache as it is.
2949
+ * Composes a query string to be added to the endpoint of this provider.
3129
2950
  *
3130
- * @param cacheId {string}
3131
- * @return {any}
3132
- */
3133
- ;
3134
- _proto.getCached = function getCached(cacheId) {
3135
- try {
3136
- return this._cache.get(cacheId);
3137
- } catch (e) {
3138
- improveAndRethrow(e, "getCached");
3139
- }
3140
- };
3141
- _proto._getTtl = function _getTtl() {
3142
- return this._removeExpiredCacheAutomatically ? this._cacheTtlMs : null;
2951
+ * @param params {any[]} params array passed to the RobustExternalAPICallerService
2952
+ * @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
2953
+ * @returns {string} query string to be concatenated with endpoint
2954
+ */;
2955
+ _proto.composeQueryString = function composeQueryString(params, subRequestIndex) {
2956
+ return "";
3143
2957
  }
3144
2958
 
3145
2959
  /**
3146
- * Directly acquires the lock despite on cached data availability.
3147
- * So if this method returns result === true you can start the data retrieval.
2960
+ * Composes a body to be added to the request
3148
2961
  *
3149
- * @param cacheId {string}
3150
- * @return {Promise<{ result: true, lockId: string }|{ result: false }>}
2962
+ * @param params {any[]} params array passed to the RobustExternalAPICallerService
2963
+ * @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
2964
+ * @returns {string}
3151
2965
  */;
3152
- _proto.acquireLock = function acquireLock(cacheId) {
3153
- try {
3154
- var _this2 = this;
3155
- return Promise.resolve(_catch$5(function () {
3156
- return Promise.resolve(_this2._requestsManager.acquireLock(cacheId));
3157
- }, function (e) {
3158
- improveAndRethrow(e, "acquireLock");
3159
- }));
3160
- } catch (e) {
3161
- return Promise.reject(e);
3162
- }
2966
+ _proto.composeBody = function composeBody(params, subRequestIndex) {
2967
+ return "";
3163
2968
  }
2969
+
3164
2970
  /**
3165
- * This method should be called only if you acquired a lock successfully.
3166
- *
3167
- * If the current lock id is not equal to the passed one the passed data will be ignored.
3168
- * Or you can do the synchronous data merging on your side and pass the
3169
- * wasDataMergedSynchronouslyWithMostRecentCacheState=true so your data will be stored
3170
- * despite on the lockId.
3171
- * WARNING: you should do this only if you are sure you perform the synchronous update.
2971
+ * Extracts data from the response and returns it
3172
2972
  *
3173
- * @param cacheId {string}
3174
- * @param lockId {string}
3175
- * @param data {any}
3176
- * @param [sessionDependentData=true] {boolean}
3177
- * @param [wasDataMergedSynchronouslyWithMostRecentCacheState=false]
3178
- */
3179
- ;
3180
- _proto.saveCachedData = function saveCachedData(cacheId, lockId, data, sessionDependentData, wasDataMergedSynchronouslyWithMostRecentCacheState) {
3181
- if (sessionDependentData === void 0) {
3182
- sessionDependentData = true;
3183
- }
3184
- if (wasDataMergedSynchronouslyWithMostRecentCacheState === void 0) {
3185
- wasDataMergedSynchronouslyWithMostRecentCacheState = false;
3186
- }
3187
- try {
3188
- if (wasDataMergedSynchronouslyWithMostRecentCacheState || this._requestsManager.isTheLockActiveOne(cacheId, lockId)) {
3189
- /* We save passed data only if the <caller> has the currently acquired lockId.
3190
- * If the passed lockId is not the active one it means that other code cleared/stopped the lock
3191
- * acquired by the <caller> recently due to some urgent/more prior changes.
3192
- *
3193
- * But we allow user to pass the 'wasDataMergedSynchronouslyWithMostRecentCacheState' flag
3194
- * that tells us that the user had taken the most recent cache value and merged his new data
3195
- * with that cached value (AFTER possibly performing async data retrieval). This means that we
3196
- * can ignore the fact that his lockId is no more relevant and save the passed data
3197
- * as it is synchronously merged with the most recent cached data. (Synchronously merged means that
3198
- * the lost update cannot occur during the merge time as JS execute the synchronous functions\
3199
- * till the end).
3200
- */
3201
- if (sessionDependentData) {
3202
- this._cache.putSessionDependentData(cacheId, data, this._getTtl());
3203
- } else {
3204
- this._cache.put(cacheId, data, this._getTtl());
3205
- }
3206
- }
3207
- } catch (e) {
3208
- improveAndRethrow(e, this._bio + ".saveCachedData");
3209
- }
2973
+ * @param response {Object} HTTP response returned by provider
2974
+ * @param [params] {any[]} params array passed to the RobustExternalAPICallerService
2975
+ * @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
2976
+ * @param iterationsData {any[]} array of data retrieved from previous sub-requests
2977
+ * @returns {any}
2978
+ */;
2979
+ _proto.getDataByResponse = function getDataByResponse(response, params, subRequestIndex, iterationsData) {
2980
+ return [];
3210
2981
  }
3211
2982
 
3212
2983
  /**
3213
- * Should be called then and only then if you successfully acquired a lock with the lock id.
2984
+ * Function changing the query string according to page number and previous response
2985
+ * Only for endpoints supporting pagination
3214
2986
  *
3215
- * @param cacheId {string}
3216
- * @param lockId {string}
2987
+ * @param params {any[]} params array passed to the RobustExternalAPICallerService
2988
+ * @param previousResponse {Object} HTTP response returned by provider for previous call (previous page)
2989
+ * @param pageNumber {number} new page number. We count from 0. You need to manually increment with 1 if your
2990
+ * provider counts pages starting with 1
2991
+ * @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
2992
+ * @returns {any[]}
3217
2993
  */;
3218
- _proto.releaseLock = function releaseLock(cacheId, lockId) {
3219
- try {
3220
- if (this._requestsManager.isTheLockActiveOne(cacheId, lockId)) {
3221
- this._requestsManager.finishActiveCalculation(cacheId);
3222
- }
3223
- } catch (e) {
3224
- improveAndRethrow(e, this._bio + ".releaseLock");
3225
- }
2994
+ _proto.changeQueryParametersForPageNumber = function changeQueryParametersForPageNumber(params, previousResponse, pageNumber, subRequestIndex) {
2995
+ return params;
3226
2996
  }
3227
2997
 
3228
2998
  /**
3229
- * Actualized currently present cached data by key. Applies the provided function to the cached data.
2999
+ * Function checking whether the response is for the last page to stop requesting for a next page.
3000
+ * Only for endpoints supporting pagination.
3230
3001
  *
3231
- * @param cacheId {string} id of cache entry
3232
- * @param synchronousCurrentCacheProcessor (function|null} synchronous function accepting cache entry. Should return
3233
- * an object in following format:
3234
- * {
3235
- * isModified: boolean,
3236
- * data: any
3237
- * }
3238
- * the flag signals whether data was changed during the processing or not
3239
- * @param [sessionDependent=true] {boolean} whether to mark the cache entry as session-dependent
3002
+ * @param previousResponse {Object} HTTP response returned by provider for previous call (previous page)
3003
+ * @param currentResponse {Object} HTTP response returned by provider for current call (current page, next after the previous)
3004
+ * @param currentPageNumber {number} current page number (for current response)
3005
+ * @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
3006
+ * @returns {boolean}
3240
3007
  */;
3241
- _proto.actualizeCachedData = function actualizeCachedData(cacheId, synchronousCurrentCacheProcessor, sessionDependent) {
3242
- if (sessionDependent === void 0) {
3243
- sessionDependent = true;
3244
- }
3245
- try {
3246
- var cached = this._cache.get(cacheId);
3247
- var result = synchronousCurrentCacheProcessor(cached);
3248
- if (result != null && result.isModified && (result == null ? void 0 : result.data) != null) {
3249
- if (sessionDependent) {
3250
- this._cache.putSessionDependentData(cacheId, result == null ? void 0 : result.data, this._getTtl());
3251
- } else {
3252
- this._cache.put(cacheId, result == null ? void 0 : result.data, this._getTtl());
3253
- }
3008
+ _proto.checkWhetherResponseIsForLastPage = function checkWhetherResponseIsForLastPage(previousResponse, currentResponse, currentPageNumber, subRequestIndex) {
3009
+ return true;
3010
+ }
3254
3011
 
3255
- /* Here we call the lock releasing to ensure the currently active calculation will be ignored.
3256
- * This is needed to ensure no 'lost update'.
3257
- * Lost update can occur if we change data in this method and after that some calculation finishes
3258
- * having the earlier data as its base to calculate its data set result. And the earlier data
3259
- * has no changes applied inside this method, so we will lose them.
3260
- *
3261
- * This is not so good solution: ideally, we should acquire lock before performing any data updating.
3262
- * But the goal of this method is to provide an instant ability to update the cached data.
3263
- * And if we start acquiring the lock here the data update can be postponed significantly.
3264
- * And this kills the desired nature of this method.
3265
- * So we better lose some data retrieval (means abusing the resource a bit) than lose
3266
- * the instant update expected after this method execution.
3267
- */
3268
- this._requestsManager.finishActiveCalculation(cacheId);
3269
- }
3270
- } catch (e) {
3271
- improveAndRethrow(e, this._bio + ".actualizeCachedData");
3272
- }
3273
- };
3274
- _proto.invalidate = function invalidate(key) {
3275
- this._cache.invalidate(key);
3276
- this._requestsManager.finishActiveCalculation(key);
3277
- };
3278
- _proto.invalidateContaining = function invalidateContaining(keyPart) {
3279
- this._cache.invalidateContaining(keyPart);
3280
- this._requestsManager.finishAllActiveCalculations(keyPart);
3012
+ /**
3013
+ * Resets the nice factor to default value
3014
+ */;
3015
+ _proto.resetNiceFactor = function resetNiceFactor() {
3016
+ this.niceFactor = 1;
3017
+ }
3018
+
3019
+ /**
3020
+ * Internal method used for requests requiring sub-requests.
3021
+ *
3022
+ * @param iterationsData {any[]} iterations data retrieved from getDataByResponse called per sub-request.
3023
+ * @return {any} by default flatten the passed iterations data array. Should be redefined if you need another logic.
3024
+ */;
3025
+ _proto.incorporateIterationsData = function incorporateIterationsData(iterationsData) {
3026
+ return iterationsData.flat();
3281
3027
  };
3282
- _proto.markAsExpiredButDontRemove = function markAsExpiredButDontRemove(key) {
3283
- if (this._removeExpiredCacheAutomatically) {
3284
- this._cache.markCacheItemAsExpiredButDontRemove(key, this._cacheTtlMs);
3285
- } else {
3286
- this._cache.setLastUpdateTimestamp(key, Date.now() - this._cacheTtlMs - 1);
3028
+ return ExternalApiProvider;
3029
+ }();
3030
+
3031
+ /**
3032
+ * Models a group of APIs provided by the same owner and used for different services in our app.
3033
+ * It means we need to mention RPS several times for each usage and also have some holder of last call timestamp per
3034
+ * api group. So this concept allows to use it for exact ExternalApiProvider and make sure that you use the same
3035
+ * RPS value and make decisions on base of the same timestamp of last call to the API group owner.
3036
+ */
3037
+ var ApiGroup = /*#__PURE__*/function () {
3038
+ function ApiGroup(id, rps, backendProxyIdGenerator) {
3039
+ if (backendProxyIdGenerator === void 0) {
3040
+ backendProxyIdGenerator = null;
3287
3041
  }
3288
- this._requestsManager.finishAllActiveCalculations(key);
3042
+ this.id = id;
3043
+ this.rps = rps;
3044
+ this.lastCalledTimestamp = null;
3045
+ this.backendProxyIdGenerator = backendProxyIdGenerator;
3046
+ }
3047
+ var _proto = ApiGroup.prototype;
3048
+ _proto.isRpsExceeded = function isRpsExceeded() {
3049
+ var _this$lastCalledTimes;
3050
+ return ((_this$lastCalledTimes = this.lastCalledTimestamp) != null ? _this$lastCalledTimes : 0) + Math.floor(1000 / this.rps) > Date.now();
3289
3051
  };
3290
- return CacheAndConcurrentRequestsResolver;
3052
+ _proto.actualizeLastCalledTimestamp = function actualizeLastCalledTimestamp() {
3053
+ this.lastCalledTimestamp = Date.now();
3054
+ };
3055
+ return ApiGroup;
3291
3056
  }();
3292
- var ManagerOfRequestsToTheSameResource = /*#__PURE__*/function () {
3057
+ var ApiGroups = {
3293
3058
  /**
3294
- * @param bio {string} resource-related identifier for logging
3295
- * @param [maxPollsCount=100] {number} max number of attempts to wait when waiting for a lock acquisition
3296
- * @param [timeoutDuration=1000] {number} timeout between the polls for a lock acquisition
3059
+ * Currently we use free version of etherscan provider with 0.2 RPS. But we have API key with 100k requests free
3060
+ * per month. So we can add it if not enough current RPS.
3297
3061
  */
3298
- function ManagerOfRequestsToTheSameResource(bio, maxPollsCount, timeoutDuration) {
3299
- if (maxPollsCount === void 0) {
3300
- maxPollsCount = 100;
3062
+ ETHERSCAN: new ApiGroup("etherscan", 0.17),
3063
+ // Actually 0.2 but fails sometime, so we use smaller
3064
+ ALCHEMY: new ApiGroup("alchemy", 0.3, function (networkKey) {
3065
+ return "alchemy-" + networkKey;
3066
+ }),
3067
+ BLOCKSTREAM: new ApiGroup("blockstream", 0.2),
3068
+ BLOCKCHAIN_INFO: new ApiGroup("blockchain.info", 1),
3069
+ BLOCKNATIVE: new ApiGroup("blocknative", 0.5),
3070
+ ETHGASSTATION: new ApiGroup("ethgasstation", 0.5),
3071
+ TRONGRID: new ApiGroup("trongrid", 0.3, function (networkKey) {
3072
+ return "trongrid-" + networkKey;
3073
+ }),
3074
+ TRONSCAN: new ApiGroup("tronscan", 0.3),
3075
+ GETBLOCK: new ApiGroup("getblock", 0.3),
3076
+ COINCAP: new ApiGroup("coincap", 0.5),
3077
+ // 200 per minute without API key
3078
+ COINGECKO: new ApiGroup("coingecko", 0.9),
3079
+ // actually 0.13-0.5 according to the docs but we use smaller due to expirienced frequent abuses
3080
+ MESSARI: new ApiGroup("messari", 0.2),
3081
+ BTCCOM: new ApiGroup("btccom", 0.2),
3082
+ BITAPS: new ApiGroup("bitaps", 0.25),
3083
+ // Docs say that RPS is 3 but using it causes frequent 429 HTTP errors
3084
+ CEX: new ApiGroup("cex", 0.5),
3085
+ // Just assumption for RPS
3086
+ BIGDATACLOUD: new ApiGroup("bigdatacloud", 1),
3087
+ // Just assumption for RPS
3088
+ TRACKIP: new ApiGroup("trackip", 1),
3089
+ // Just assumption for RPS
3090
+ IPIFY: new ApiGroup("ipify", 1),
3091
+ // Just assumption for RPS
3092
+ WHATISMYIPADDRESS: new ApiGroup("whatismyipaddress", 1),
3093
+ // Just assumption for RPS
3094
+ EXCHANGERATE: new ApiGroup("exchangerate", 1),
3095
+ // Just assumption for RPS
3096
+ FRANKFURTER: new ApiGroup("frankfurter", 1),
3097
+ // Just assumption for RPS
3098
+ BITGO: new ApiGroup("bitgo", 1),
3099
+ // Just assumption for RPS
3100
+ BITCOINER: new ApiGroup("bitcoiner", 1),
3101
+ // Just assumption for RPS
3102
+ BITCORE: new ApiGroup("bitcore", 1),
3103
+ // Just assumption for RPS
3104
+ // BLOCKCHAIR: new ApiGroup("blockchair", 0.04), // this provider require API key for commercial use (10usd 10000 reqs), we will add it later
3105
+ MEMPOOL: new ApiGroup("mempool", 0.2) // Just assumption for RPS
3106
+ };
3107
+
3108
+ // TODO: [refactoring, low] Consider removing this logic task_id=c360f2af75764bde8badd9ff1cc00d48
3109
+ var ConcurrentCalculationsMetadataHolder = /*#__PURE__*/function () {
3110
+ function ConcurrentCalculationsMetadataHolder() {
3111
+ this._calculations = {};
3112
+ }
3113
+ var _proto = ConcurrentCalculationsMetadataHolder.prototype;
3114
+ _proto.startCalculation = function startCalculation(domain, calculationsHistoryMaxLength) {
3115
+ if (calculationsHistoryMaxLength === void 0) {
3116
+ calculationsHistoryMaxLength = 100;
3301
3117
  }
3302
- if (timeoutDuration === void 0) {
3303
- timeoutDuration = 1000;
3118
+ if (!this._calculations[domain]) {
3119
+ this._calculations[domain] = [];
3120
+ }
3121
+ if (this._calculations[domain].length > calculationsHistoryMaxLength) {
3122
+ this._calculations[domain] = this._calculations[domain].slice(Math.round(calculationsHistoryMaxLength * 0.2));
3123
+ }
3124
+ var newCalculation = {
3125
+ startTimestamp: Date.now(),
3126
+ endTimestamp: null,
3127
+ uuid: v4()
3128
+ };
3129
+ this._calculations[domain].push(newCalculation);
3130
+ return newCalculation.uuid;
3131
+ };
3132
+ _proto.endCalculation = function endCalculation(domain, uuid, isFailed) {
3133
+ if (isFailed === void 0) {
3134
+ isFailed = false;
3304
3135
  }
3305
- this.bio = bio;
3306
- this.maxPollsCount = maxPollsCount;
3307
- this.timeoutDuration = timeoutDuration;
3308
- this._activeCalculationsIds = new Map();
3309
- this._nextCalculationIds = new Map();
3310
- }
3311
-
3312
- /**
3313
- * If there is no active calculation just creates uuid and returns it.
3314
- * If there is active calculation waits until it removed from the active calculation uuid variable.
3315
- *
3316
- * @param requestHash {string}
3317
- * @return {Promise<string|boolean>} returns uuid of new active calculation or true if waiting for active
3318
- * calculation succeed or false if max attempts count exceeded
3319
- */
3320
- var _proto2 = ManagerOfRequestsToTheSameResource.prototype;
3321
- _proto2.startCalculationOrWaitForActiveToFinish = function startCalculationOrWaitForActiveToFinish(requestHash) {
3322
3136
  try {
3323
- var _exit;
3324
- var _this3 = this;
3325
- var _temp3 = _catch$5(function () {
3326
- var activeCalculationIdForHash = _this3._activeCalculationsIds.get(requestHash);
3327
- if (activeCalculationIdForHash == null) {
3328
- var id = v4();
3329
- _this3._activeCalculationsIds.set(requestHash, id);
3330
- _exit = 1;
3331
- return id;
3332
- }
3333
- return Promise.resolve(_this3._waitForCalculationIdToFinish(requestHash, activeCalculationIdForHash, 0)).then(function (_await$_this3$_waitFo) {
3334
- _exit = 1;
3335
- return _await$_this3$_waitFo;
3336
- });
3337
- }, function (e) {
3338
- Logger.logError(e, "startCalculationOrWaitForActiveToFinish_" + _this3.bio);
3137
+ var _calculation$endTimes, _calculation$startTim, _calculation$uuid;
3138
+ var calculation = this._calculations[domain].find(function (calculation) {
3139
+ return (calculation == null ? void 0 : calculation.uuid) === uuid;
3339
3140
  });
3340
- return Promise.resolve(_temp3 && _temp3.then ? _temp3.then(function (_result3) {
3341
- return _exit ? _result3 : null;
3342
- }) : _exit ? _temp3 : null);
3141
+ if (calculation) {
3142
+ calculation.endTimestamp = Date.now();
3143
+ calculation.isFiled = isFailed;
3144
+ }
3145
+ var elapsed = ((((_calculation$endTimes = calculation == null ? void 0 : calculation.endTimestamp) != null ? _calculation$endTimes : 0) - ((_calculation$startTim = calculation == null ? void 0 : calculation.startTimestamp) != null ? _calculation$startTim : 0)) / 1000).toFixed(1);
3146
+ Logger.log("endCalculation", elapsed + " ms: " + domain + "." + ((_calculation$uuid = calculation == null ? void 0 : calculation.uuid) != null ? _calculation$uuid : "").slice(0, 7));
3147
+ return calculation;
3343
3148
  } catch (e) {
3344
- return Promise.reject(e);
3345
- }
3346
- }
3347
- /**
3348
- * Acquires lock to the resource by the provided hash.
3349
- *
3350
- * @param requestHash {string}
3351
- * @return {Promise<{ result: true, lockId: string }|{ result: false }>} result is true if the lock is successfully
3352
- * acquired, false if the max allowed time to wait for acquisition expired or any unexpected error occurs
3353
- * during the waiting.
3354
- */
3355
- ;
3356
- _proto2.acquireLock = function acquireLock(requestHash) {
3357
- try {
3358
- var _this4 = this;
3359
- return Promise.resolve(_catch$5(function () {
3360
- var _this4$_nextCalculati;
3361
- var activeId = _this4._activeCalculationsIds.get(requestHash);
3362
- var nextId = v4();
3363
- if (activeId == null) {
3364
- _this4._activeCalculationsIds.set(requestHash, nextId);
3365
- return {
3366
- result: true,
3367
- lockId: nextId
3368
- };
3369
- }
3370
- var currentNext = (_this4$_nextCalculati = _this4._nextCalculationIds.get(requestHash)) != null ? _this4$_nextCalculati : [];
3371
- currentNext.push(nextId);
3372
- _this4._nextCalculationIds.set(requestHash, currentNext);
3373
- return Promise.resolve(_this4._waitForCalculationIdToFinish(requestHash, activeId, 0, nextId)).then(function (waitingResult) {
3374
- return {
3375
- result: waitingResult,
3376
- lockId: waitingResult ? nextId : undefined
3377
- };
3378
- });
3379
- }, function (e) {
3380
- improveAndRethrow(e, "acquireLock");
3381
- }));
3382
- } catch (e) {
3383
- return Promise.reject(e);
3384
- }
3385
- }
3386
- /**
3387
- * Clears active calculation id.
3388
- * WARNING: if you forget to call this method the start* one will perform maxPollsCount attempts before finishing
3389
- * @param requestHash {string} hash of request. Helps to distinct the request for the same resource but
3390
- * having different request parameters and hold a dedicated calculation id per this hash
3391
- */
3392
- ;
3393
- _proto2.finishActiveCalculation = function finishActiveCalculation(requestHash) {
3394
- if (requestHash === void 0) {
3395
- requestHash = "default";
3396
- }
3397
- try {
3398
- var _this$_nextCalculatio;
3399
- this._activeCalculationsIds["delete"](requestHash);
3400
- var next = (_this$_nextCalculatio = this._nextCalculationIds.get(requestHash)) != null ? _this$_nextCalculatio : [];
3401
- if (next.length) {
3402
- this._activeCalculationsIds.set(requestHash, next[0]);
3403
- this._nextCalculationIds.set(requestHash, next.slice(1));
3404
- }
3405
- } catch (e) {
3406
- improveAndRethrow(e, "finishActiveCalculation");
3407
- }
3408
- };
3409
- _proto2.finishAllActiveCalculations = function finishAllActiveCalculations(keyPart) {
3410
- var _this5 = this;
3411
- if (keyPart === void 0) {
3412
- keyPart = "";
3413
- }
3414
- try {
3415
- Array.from(this._activeCalculationsIds.keys()).forEach(function (hash) {
3416
- if (typeof hash === "string" && new RegExp(keyPart).test(hash)) {
3417
- _this5.finishActiveCalculation(hash);
3418
- }
3419
- });
3420
- } catch (e) {
3421
- improveAndRethrow(e, "finishAllActiveCalculations");
3422
- }
3423
- }
3424
-
3425
- /**
3426
- * @param requestHash {string}
3427
- * @param lockId {string}
3428
- * @return {boolean}
3429
- */;
3430
- _proto2.isTheLockActiveOne = function isTheLockActiveOne(requestHash, lockId) {
3431
- try {
3432
- return this._activeCalculationsIds.get(requestHash) === lockId;
3433
- } catch (e) {
3434
- improveAndRethrow(e, "isTheLockActiveOne");
3435
- }
3436
- }
3437
-
3438
- /**
3439
- * @param requestHash {string}
3440
- * @param activeCalculationId {string|null}
3441
- * @param [attemptIndex=0] {number}
3442
- * @param waitForCalculationId {string|null} if you want to wait for an exact id to appear as active then pass this parameter
3443
- * @return {Promise<boolean>} true
3444
- * - if the given calculation id is no more an active one
3445
- * - or it is equal to waitForCalculationId
3446
- * false
3447
- * - if waiting period exceeds the max allowed waiting time or unexpected error occurs
3448
- * @private
3449
- */;
3450
- _proto2._waitForCalculationIdToFinish = function _waitForCalculationIdToFinish(requestHash, activeCalculationId, attemptIndex, waitForCalculationId) {
3451
- if (attemptIndex === void 0) {
3452
- attemptIndex = 0;
3453
- }
3454
- if (waitForCalculationId === void 0) {
3455
- waitForCalculationId = null;
3456
- }
3457
- try {
3458
- var _this6 = this;
3459
- try {
3460
- if (attemptIndex + 1 > _this6.maxPollsCount) {
3461
- // Max number of polls for active calculation id change is achieved. So we return false.
3462
- return Promise.resolve(false);
3463
- }
3464
- var currentId = _this6._activeCalculationsIds.get(requestHash);
3465
- if (waitForCalculationId == null ? currentId !== activeCalculationId : currentId === waitForCalculationId) {
3466
- /* We return true depending on the usage of this function:
3467
- * 1. if there is calculation id that we should wait for to become an active then we return true only
3468
- * if this id becomes the active one.
3469
- *
3470
- * Theoretically we can fail to wait for the desired calculation id. This can be caused by wrong use of
3471
- * this service or by any other mistakes/errors. But this waiting function will return false anyway if
3472
- * the number of polls done exceeds the max allowed.
3473
- *
3474
- * 2. if we just wait for the currently active calculation id to be finished then we return true
3475
- * when we notice that the current active id differs from the original passed into this function.
3476
- */
3477
- return Promise.resolve(true);
3478
- } else {
3479
- /* The original calculation id is still the active one, so we are scheduling a new attempt to check
3480
- * whether the active calculation id changed or not in timeoutDuration milliseconds.
3481
- */
3482
- var it = _this6;
3483
- return Promise.resolve(new Promise(function (resolve, reject) {
3484
- setTimeout(function () {
3485
- try {
3486
- resolve(it._waitForCalculationIdToFinish(requestHash, activeCalculationId, attemptIndex + 1));
3487
- } catch (e) {
3488
- reject(e);
3489
- }
3490
- }, _this6.timeoutDuration);
3491
- }));
3492
- }
3493
- } catch (e) {
3494
- Logger.logError(e, "_waitForCalculationIdToFinish", "Failed to wait for active calculation id change.");
3495
- return Promise.resolve(false);
3496
- }
3497
- } catch (e) {
3498
- return Promise.reject(e);
3499
- }
3500
- };
3501
- return ManagerOfRequestsToTheSameResource;
3502
- }();
3503
-
3504
- // TODO: [refactoring, low] Consider removing this logic task_id=c360f2af75764bde8badd9ff1cc00d48
3505
- var ConcurrentCalculationsMetadataHolder = /*#__PURE__*/function () {
3506
- function ConcurrentCalculationsMetadataHolder() {
3507
- this._calculations = {};
3508
- }
3509
- var _proto = ConcurrentCalculationsMetadataHolder.prototype;
3510
- _proto.startCalculation = function startCalculation(domain, calculationsHistoryMaxLength) {
3511
- if (calculationsHistoryMaxLength === void 0) {
3512
- calculationsHistoryMaxLength = 100;
3513
- }
3514
- if (!this._calculations[domain]) {
3515
- this._calculations[domain] = [];
3516
- }
3517
- if (this._calculations[domain].length > calculationsHistoryMaxLength) {
3518
- this._calculations[domain] = this._calculations[domain].slice(Math.round(calculationsHistoryMaxLength * 0.2));
3519
- }
3520
- var newCalculation = {
3521
- startTimestamp: Date.now(),
3522
- endTimestamp: null,
3523
- uuid: v4()
3524
- };
3525
- this._calculations[domain].push(newCalculation);
3526
- return newCalculation.uuid;
3527
- };
3528
- _proto.endCalculation = function endCalculation(domain, uuid, isFailed) {
3529
- if (isFailed === void 0) {
3530
- isFailed = false;
3531
- }
3532
- try {
3533
- var _calculation$endTimes, _calculation$startTim, _calculation$uuid;
3534
- var calculation = this._calculations[domain].find(function (calculation) {
3535
- return (calculation == null ? void 0 : calculation.uuid) === uuid;
3536
- });
3537
- if (calculation) {
3538
- calculation.endTimestamp = Date.now();
3539
- calculation.isFiled = isFailed;
3540
- }
3541
- var elapsed = ((((_calculation$endTimes = calculation == null ? void 0 : calculation.endTimestamp) != null ? _calculation$endTimes : 0) - ((_calculation$startTim = calculation == null ? void 0 : calculation.startTimestamp) != null ? _calculation$startTim : 0)) / 1000).toFixed(1);
3542
- Logger.log("endCalculation", elapsed + " ms: " + domain + "." + ((_calculation$uuid = calculation == null ? void 0 : calculation.uuid) != null ? _calculation$uuid : "").slice(0, 7));
3543
- return calculation;
3544
- } catch (e) {
3545
- Logger.logError(e, "endCalculation");
3149
+ Logger.logError(e, "endCalculation");
3546
3150
  }
3547
3151
  };
3548
3152
  _proto.isCalculationLate = function isCalculationLate(domain, uuid) {
@@ -3624,61 +3228,689 @@ var ExternalServicesStatsCollector = /*#__PURE__*/function () {
3624
3228
  callsCount: 1,
3625
3229
  failsCount: 0
3626
3230
  });
3627
- }
3231
+ }
3232
+ } catch (e) {
3233
+ improveAndRethrow(e, "externalServiceCalledWithoutError");
3234
+ }
3235
+ }
3236
+
3237
+ /**
3238
+ * Returns statistics about external services failures.
3239
+ * Provides how many calls were performed and what the percent of failed calls. Also returns errors stat.
3240
+ *
3241
+ * @return {Array<object>} Array of objects of type { failsPerCent: number, calls: number }
3242
+ * sorted by the highest fails percent desc
3243
+ */;
3244
+ _proto.getStats = function getStats() {
3245
+ var _this = this;
3246
+ try {
3247
+ return Array.from(this.stats.keys()).map(function (key) {
3248
+ var _stat$errors2;
3249
+ var stat = _this.stats.get(key);
3250
+ return {
3251
+ url: key,
3252
+ failsPerCent: (stat.failsCount / stat.callsCount * 100).toFixed(2),
3253
+ calls: stat.callsCount,
3254
+ errors: (_stat$errors2 = stat.errors) != null ? _stat$errors2 : []
3255
+ };
3256
+ }).sort(function (s1, s2) {
3257
+ return s1.failsPerCent - s2.failsPerCent;
3258
+ });
3259
+ } catch (e) {
3260
+ Logger.logError(e, "getStats");
3261
+ }
3262
+ };
3263
+ return ExternalServicesStatsCollector;
3264
+ }();
3265
+
3266
+ /**
3267
+ * TODO: [refactoring, critical] update backend copy of this service. Also there is a task to extract this
3268
+ * service and other related to it stuff to dedicated npm package task_id=b008ee5e4a3f42c08c73831c4bb3db4e
3269
+ *
3270
+ * Template service needed to avoid duplication of the same logic when we need to call
3271
+ * external APIs to retrieve some data. The idea is to use several API providers to retrieve the same data. It helps to
3272
+ * improve the reliability of a data retrieval.
3273
+ */
3274
+
3275
+ function _catch$6(body, recover) {
3276
+ try {
3277
+ var result = body();
3278
+ } catch (e) {
3279
+ return recover(e);
3280
+ }
3281
+ if (result && result.then) {
3282
+ return result.then(void 0, recover);
3283
+ }
3284
+ return result;
3285
+ }
3286
+ function _settle$2(pact, state, value) {
3287
+ if (!pact.s) {
3288
+ if (value instanceof _Pact$2) {
3289
+ if (value.s) {
3290
+ if (state & 1) {
3291
+ state = value.s;
3292
+ }
3293
+ value = value.v;
3294
+ } else {
3295
+ value.o = _settle$2.bind(null, pact, state);
3296
+ return;
3297
+ }
3298
+ }
3299
+ if (value && value.then) {
3300
+ value.then(_settle$2.bind(null, pact, state), _settle$2.bind(null, pact, 2));
3301
+ return;
3302
+ }
3303
+ pact.s = state;
3304
+ pact.v = value;
3305
+ var observer = pact.o;
3306
+ if (observer) {
3307
+ observer(pact);
3308
+ }
3309
+ }
3310
+ }
3311
+ var _Pact$2 = /*#__PURE__*/function () {
3312
+ function _Pact() {}
3313
+ _Pact.prototype.then = function (onFulfilled, onRejected) {
3314
+ var result = new _Pact();
3315
+ var state = this.s;
3316
+ if (state) {
3317
+ var callback = state & 1 ? onFulfilled : onRejected;
3318
+ if (callback) {
3319
+ try {
3320
+ _settle$2(result, 1, callback(this.v));
3321
+ } catch (e) {
3322
+ _settle$2(result, 2, e);
3323
+ }
3324
+ return result;
3325
+ } else {
3326
+ return this;
3327
+ }
3328
+ }
3329
+ this.o = function (_this) {
3330
+ try {
3331
+ var value = _this.v;
3332
+ if (_this.s & 1) {
3333
+ _settle$2(result, 1, onFulfilled ? onFulfilled(value) : value);
3334
+ } else if (onRejected) {
3335
+ _settle$2(result, 1, onRejected(value));
3336
+ } else {
3337
+ _settle$2(result, 2, value);
3338
+ }
3339
+ } catch (e) {
3340
+ _settle$2(result, 2, e);
3341
+ }
3342
+ };
3343
+ return result;
3344
+ };
3345
+ return _Pact;
3346
+ }();
3347
+ function _isSettledPact$2(thenable) {
3348
+ return thenable instanceof _Pact$2 && thenable.s & 1;
3349
+ }
3350
+ function _for$1(test, update, body) {
3351
+ var stage;
3352
+ for (;;) {
3353
+ var shouldContinue = test();
3354
+ if (_isSettledPact$2(shouldContinue)) {
3355
+ shouldContinue = shouldContinue.v;
3356
+ }
3357
+ if (!shouldContinue) {
3358
+ return result;
3359
+ }
3360
+ if (shouldContinue.then) {
3361
+ stage = 0;
3362
+ break;
3363
+ }
3364
+ var result = body();
3365
+ if (result && result.then) {
3366
+ if (_isSettledPact$2(result)) {
3367
+ result = result.s;
3368
+ } else {
3369
+ stage = 1;
3370
+ break;
3371
+ }
3372
+ }
3373
+ if (update) {
3374
+ var updateValue = update();
3375
+ if (updateValue && updateValue.then && !_isSettledPact$2(updateValue)) {
3376
+ stage = 2;
3377
+ break;
3378
+ }
3379
+ }
3380
+ }
3381
+ var pact = new _Pact$2();
3382
+ var reject = _settle$2.bind(null, pact, 2);
3383
+ (stage === 0 ? shouldContinue.then(_resumeAfterTest) : stage === 1 ? result.then(_resumeAfterBody) : updateValue.then(_resumeAfterUpdate)).then(void 0, reject);
3384
+ return pact;
3385
+ function _resumeAfterBody(value) {
3386
+ result = value;
3387
+ do {
3388
+ if (update) {
3389
+ updateValue = update();
3390
+ if (updateValue && updateValue.then && !_isSettledPact$2(updateValue)) {
3391
+ updateValue.then(_resumeAfterUpdate).then(void 0, reject);
3392
+ return;
3393
+ }
3394
+ }
3395
+ shouldContinue = test();
3396
+ if (!shouldContinue || _isSettledPact$2(shouldContinue) && !shouldContinue.v) {
3397
+ _settle$2(pact, 1, result);
3398
+ return;
3399
+ }
3400
+ if (shouldContinue.then) {
3401
+ shouldContinue.then(_resumeAfterTest).then(void 0, reject);
3402
+ return;
3403
+ }
3404
+ result = body();
3405
+ if (_isSettledPact$2(result)) {
3406
+ result = result.v;
3407
+ }
3408
+ } while (!result || !result.then);
3409
+ result.then(_resumeAfterBody).then(void 0, reject);
3410
+ }
3411
+ function _resumeAfterTest(shouldContinue) {
3412
+ if (shouldContinue) {
3413
+ result = body();
3414
+ if (result && result.then) {
3415
+ result.then(_resumeAfterBody).then(void 0, reject);
3416
+ } else {
3417
+ _resumeAfterBody(result);
3418
+ }
3419
+ } else {
3420
+ _settle$2(pact, 1, result);
3421
+ }
3422
+ }
3423
+ function _resumeAfterUpdate() {
3424
+ if (shouldContinue = test()) {
3425
+ if (shouldContinue.then) {
3426
+ shouldContinue.then(_resumeAfterTest).then(void 0, reject);
3427
+ } else {
3428
+ _resumeAfterTest(shouldContinue);
3429
+ }
3430
+ } else {
3431
+ _settle$2(pact, 1, result);
3432
+ }
3433
+ }
3434
+ }
3435
+ function _finallyRethrows$1(body, finalizer) {
3436
+ try {
3437
+ var result = body();
3438
+ } catch (e) {
3439
+ return finalizer(true, e);
3440
+ }
3441
+ if (result && result.then) {
3442
+ return result.then(finalizer.bind(null, false), finalizer.bind(null, true));
3443
+ }
3444
+ return finalizer(false, result);
3445
+ }
3446
+ function _do(body, test) {
3447
+ var awaitBody;
3448
+ do {
3449
+ var result = body();
3450
+ if (result && result.then) {
3451
+ if (_isSettledPact$2(result)) {
3452
+ result = result.v;
3453
+ } else {
3454
+ awaitBody = true;
3455
+ break;
3456
+ }
3457
+ }
3458
+ var shouldContinue = test();
3459
+ if (_isSettledPact$2(shouldContinue)) {
3460
+ shouldContinue = shouldContinue.v;
3461
+ }
3462
+ if (!shouldContinue) {
3463
+ return result;
3464
+ }
3465
+ } while (!shouldContinue.then);
3466
+ var pact = new _Pact$2();
3467
+ var reject = _settle$2.bind(null, pact, 2);
3468
+ (awaitBody ? result.then(_resumeAfterBody) : shouldContinue.then(_resumeAfterTest)).then(void 0, reject);
3469
+ return pact;
3470
+ function _resumeAfterBody(value) {
3471
+ result = value;
3472
+ for (;;) {
3473
+ shouldContinue = test();
3474
+ if (_isSettledPact$2(shouldContinue)) {
3475
+ shouldContinue = shouldContinue.v;
3476
+ }
3477
+ if (!shouldContinue) {
3478
+ break;
3479
+ }
3480
+ if (shouldContinue.then) {
3481
+ shouldContinue.then(_resumeAfterTest).then(void 0, reject);
3482
+ return;
3483
+ }
3484
+ result = body();
3485
+ if (result && result.then) {
3486
+ if (_isSettledPact$2(result)) {
3487
+ result = result.v;
3488
+ } else {
3489
+ result.then(_resumeAfterBody).then(void 0, reject);
3490
+ return;
3491
+ }
3492
+ }
3493
+ }
3494
+ _settle$2(pact, 1, result);
3495
+ }
3496
+ function _resumeAfterTest(shouldContinue) {
3497
+ if (shouldContinue) {
3498
+ do {
3499
+ result = body();
3500
+ if (result && result.then) {
3501
+ if (_isSettledPact$2(result)) {
3502
+ result = result.v;
3503
+ } else {
3504
+ result.then(_resumeAfterBody).then(void 0, reject);
3505
+ return;
3506
+ }
3507
+ }
3508
+ shouldContinue = test();
3509
+ if (_isSettledPact$2(shouldContinue)) {
3510
+ shouldContinue = shouldContinue.v;
3511
+ }
3512
+ if (!shouldContinue) {
3513
+ _settle$2(pact, 1, result);
3514
+ return;
3515
+ }
3516
+ } while (!shouldContinue.then);
3517
+ shouldContinue.then(_resumeAfterTest).then(void 0, reject);
3518
+ } else {
3519
+ _settle$2(pact, 1, result);
3520
+ }
3521
+ }
3522
+ }
3523
+ function _forTo$1(array, body, check) {
3524
+ var i = -1,
3525
+ pact,
3526
+ reject;
3527
+ function _cycle(result) {
3528
+ try {
3529
+ while (++i < array.length && (!check || !check())) {
3530
+ result = body(i);
3531
+ if (result && result.then) {
3532
+ if (_isSettledPact$2(result)) {
3533
+ result = result.v;
3534
+ } else {
3535
+ result.then(_cycle, reject || (reject = _settle$2.bind(null, pact = new _Pact$2(), 2)));
3536
+ return;
3537
+ }
3538
+ }
3539
+ }
3540
+ if (pact) {
3541
+ _settle$2(pact, 1, result);
3542
+ } else {
3543
+ pact = result;
3544
+ }
3545
+ } catch (e) {
3546
+ _settle$2(pact || (pact = new _Pact$2()), 2, e);
3547
+ }
3548
+ }
3549
+ _cycle();
3550
+ return pact;
3551
+ }
3552
+ var RobustExternalAPICallerService = /*#__PURE__*/function () {
3553
+ RobustExternalAPICallerService.getStats = function getStats() {
3554
+ return this.statsCollector.getStats();
3555
+ }
3556
+
3557
+ /**
3558
+ * @param bio {string} service name for logging
3559
+ * @param providersData {ExternalApiProvider[]} array of providers
3560
+ * @param [logger] {function} function to be used for logging
3561
+ */;
3562
+ function RobustExternalAPICallerService(bio, providersData, logger) {
3563
+ providersData.forEach(function (provider) {
3564
+ if (!provider.endpoint && provider.endpoint !== "" || !provider.httpMethod) {
3565
+ throw new Error("Wrong format of providers data for: " + JSON.stringify(provider));
3566
+ }
3567
+ });
3568
+
3569
+ // We add niceFactor - just number to order the providers array by. It is helpful to call
3570
+ // less robust APIs only if more robust fails
3571
+ this.providers = providersData;
3572
+ providersData.forEach(function (provider) {
3573
+ return provider.resetNiceFactor();
3574
+ });
3575
+ this.bio = bio;
3576
+ this._logger = function () {
3577
+ Logger.logError.apply(Logger, [].slice.call(arguments));
3578
+ };
3579
+ }
3580
+ var _proto = RobustExternalAPICallerService.prototype;
3581
+ /**
3582
+ * Performs data retrieval from external APIs. Tries providers till the data is retrieved.
3583
+ *
3584
+ * @param parametersValues {array} array of values of the parameters for URL query string [and/or body]
3585
+ * @param timeoutMS {number} http timeout to wait for response. If provider has its specific timeout value then it is used
3586
+ * @param [cancelToken] {object|undefined} axios token to force-cancel requests from high-level code
3587
+ * @param [attemptsCount] {number|undefined} number of attempts to be performed
3588
+ * @param [doNotFailForNowData] {boolean|undefined} pass true if you do not want us to throw an error if we retrieved null data from all the providers
3589
+ * @return {Promise<any>} resolving to retrieved data (or array of results if specific provider requires
3590
+ * several requests. NOTE: we flatten nested arrays - results of each separate request done for the specific provider)
3591
+ * @throws Error if requests to all providers are failed
3592
+ */
3593
+ _proto.callExternalAPI = function callExternalAPI(parametersValues, timeoutMS, cancelToken, attemptsCount, doNotFailForNowData) {
3594
+ if (parametersValues === void 0) {
3595
+ parametersValues = [];
3596
+ }
3597
+ if (timeoutMS === void 0) {
3598
+ timeoutMS = 3500;
3599
+ }
3600
+ if (cancelToken === void 0) {
3601
+ cancelToken = null;
3602
+ }
3603
+ if (attemptsCount === void 0) {
3604
+ attemptsCount = 1;
3605
+ }
3606
+ if (doNotFailForNowData === void 0) {
3607
+ doNotFailForNowData = false;
3608
+ }
3609
+ try {
3610
+ var _this = this;
3611
+ var result;
3612
+ var calculationUuid = concurrentCalculationsMetadataHolder.startCalculation(_this.bio);
3613
+ return Promise.resolve(_finallyRethrows$1(function () {
3614
+ return _catch$6(function () {
3615
+ function _temp5() {
3616
+ var _result2, _result3;
3617
+ if (((_result2 = result) == null ? void 0 : _result2.data) == null) {
3618
+ // TODO: [feature, moderate] looks like we should not fail for null data as it is strange - the provider will fail when processing data internally
3619
+ var error = new Error("Failed to retrieve data. It means all attempts have been failed. DEV: add more attempts to this data retrieval");
3620
+ if (!doNotFailForNowData) {
3621
+ throw error;
3622
+ } else {
3623
+ _this._logger(error, _this.bio + ".callExternalAPI");
3624
+ }
3625
+ }
3626
+ return (_result3 = result) == null ? void 0 : _result3.data;
3627
+ }
3628
+ var i = 0;
3629
+ var _temp4 = _for$1(function () {
3630
+ var _result4, _result5;
3631
+ return (i < attemptsCount || !!((_result4 = result) != null && _result4.shouldBeForceRetried)) && ((_result5 = result) == null ? void 0 : _result5.data) == null;
3632
+ }, function () {
3633
+ return ++i;
3634
+ }, function () {
3635
+ /**
3636
+ * We use rpsFactor to improve re-attempting to call the providers if the last attempt resulted with
3637
+ * the fail due to abused RPSes of some (most part of) providers.
3638
+ * The _performCallAttempt in such a case will return increased rpsFactor inside the result object.
3639
+ */
3640
+ var rpsFactor = result ? result.rpsFactor : RobustExternalAPICallerService.defaultRPSFactor;
3641
+ result = null;
3642
+ var _temp3 = _catch$6(function () {
3643
+ function _temp2() {
3644
+ var _result$errors;
3645
+ if ((_result$errors = result.errors) != null && _result$errors.length) {
3646
+ var errors = result.errors;
3647
+ _this._logger(new Error("Failed at attempt " + i + ". " + errors.length + " errors. Messages: " + safeStringify(errors.map(function (error) {
3648
+ return error.message;
3649
+ })) + ": " + safeStringify(errors) + "."), _this.bio + ".callExternalAPI", "", true);
3650
+ }
3651
+ }
3652
+ var _temp = function (_result6) {
3653
+ if (i === 0 && !((_result6 = result) != null && _result6.shouldBeForceRetried)) {
3654
+ return Promise.resolve(_this._performCallAttempt(parametersValues, timeoutMS, cancelToken, rpsFactor, doNotFailForNowData)).then(function (_this$_performCallAtt) {
3655
+ result = _this$_performCallAtt;
3656
+ });
3657
+ } else {
3658
+ var maxRps = Math.max.apply(Math, _this.providers.map(function (provider) {
3659
+ var _provider$getRps;
3660
+ return (_provider$getRps = provider.getRps()) != null ? _provider$getRps : 0;
3661
+ }));
3662
+ var waitingTimeMs = maxRps ? 1000 / (maxRps / rpsFactor) : 0;
3663
+ return Promise.resolve(new Promise(function (resolve, reject) {
3664
+ setTimeout(function () {
3665
+ try {
3666
+ var _temp6 = _catch$6(function () {
3667
+ return Promise.resolve(_this._performCallAttempt(parametersValues, timeoutMS, cancelToken, rpsFactor, doNotFailForNowData)).then(function (_this$_performCallAtt2) {
3668
+ resolve(_this$_performCallAtt2);
3669
+ });
3670
+ }, function (e) {
3671
+ reject(e);
3672
+ });
3673
+ return Promise.resolve(_temp6 && _temp6.then ? _temp6.then(function () {}) : void 0);
3674
+ } catch (e) {
3675
+ return Promise.reject(e);
3676
+ }
3677
+ }, waitingTimeMs);
3678
+ })).then(function (_Promise) {
3679
+ result = _Promise;
3680
+ });
3681
+ }
3682
+ }();
3683
+ return _temp && _temp.then ? _temp.then(_temp2) : _temp2(_temp);
3684
+ }, function (e) {
3685
+ _this._logger(e, _this.bio + ".callExternalAPI", "Failed to perform external providers calling");
3686
+ });
3687
+ if (_temp3 && _temp3.then) return _temp3.then(function () {});
3688
+ });
3689
+ return _temp4 && _temp4.then ? _temp4.then(_temp5) : _temp5(_temp4);
3690
+ }, function (e) {
3691
+ improveAndRethrow(e, _this.bio + ".callExternalAPI");
3692
+ });
3693
+ }, function (_wasThrown, _result) {
3694
+ concurrentCalculationsMetadataHolder.endCalculation(_this.bio, calculationUuid);
3695
+ if (_wasThrown) throw _result;
3696
+ return _result;
3697
+ }));
3628
3698
  } catch (e) {
3629
- improveAndRethrow(e, "externalServiceCalledWithoutError");
3699
+ return Promise.reject(e);
3630
3700
  }
3631
- }
3632
-
3633
- /**
3634
- * Returns statistics about external services failures.
3635
- * Provides how many calls were performed and what the percent of failed calls. Also returns errors stat.
3636
- *
3637
- * @return {Array<object>} Array of objects of type { failsPerCent: number, calls: number }
3638
- * sorted by the highest fails percent desc
3639
- */;
3640
- _proto.getStats = function getStats() {
3641
- var _this = this;
3701
+ };
3702
+ _proto._performCallAttempt = function _performCallAttempt(parametersValues, timeoutMS, cancelToken, rpsFactor, doNotFailForNowData) {
3642
3703
  try {
3643
- return Array.from(this.stats.keys()).map(function (key) {
3644
- var _stat$errors2;
3645
- var stat = _this.stats.get(key);
3704
+ var _temp15 = function _temp15() {
3705
+ var _data;
3706
+ // If we are declining more than 50% of providers (by exceeding RPS) then we note that it better to retry the whole process of providers requesting
3707
+ var shouldBeForceRetried = data == null && countOfRequestsDeclinedByRps > Math.floor(providers.length * 0.5);
3708
+ var rpsMultiplier = shouldBeForceRetried ? RobustExternalAPICallerService.rpsMultiplier : 1;
3646
3709
  return {
3647
- url: key,
3648
- failsPerCent: (stat.failsCount / stat.callsCount * 100).toFixed(2),
3649
- calls: stat.callsCount,
3650
- errors: (_stat$errors2 = stat.errors) != null ? _stat$errors2 : []
3710
+ data: (_data = data) != null ? _data : null,
3711
+ shouldBeForceRetried: shouldBeForceRetried,
3712
+ rpsFactor: rpsFactor * rpsMultiplier,
3713
+ errors: errors
3651
3714
  };
3652
- }).sort(function (s1, s2) {
3653
- return s1.failsPerCent - s2.failsPerCent;
3715
+ };
3716
+ var _this2 = this;
3717
+ var providers = _this2._reorderProvidersByNiceFactor();
3718
+ var data = undefined,
3719
+ providerIndex = 0,
3720
+ countOfRequestsDeclinedByRps = 0,
3721
+ errors = [];
3722
+ var _temp14 = _for$1(function () {
3723
+ return !data && providerIndex < providers.length;
3724
+ }, void 0, function () {
3725
+ var provider = providers[providerIndex];
3726
+ if (provider.isRpsExceeded()) {
3727
+ /**
3728
+ * Current provider's RPS is exceeded, so we try next provider. Also, we count such cases to make
3729
+ * a decision about the force-retry need.
3730
+ */
3731
+ ++providerIndex;
3732
+ ++countOfRequestsDeclinedByRps;
3733
+ return;
3734
+ }
3735
+ var _temp13 = _finallyRethrows$1(function () {
3736
+ return _catch$6(function () {
3737
+ var _provider$specificHea;
3738
+ function _temp12() {
3739
+ if (iterationsData.length) {
3740
+ if (httpMethods.length > 1) {
3741
+ data = provider.incorporateIterationsData(iterationsData);
3742
+ } else {
3743
+ data = iterationsData[0];
3744
+ }
3745
+ } else if (!doNotFailForNowData) {
3746
+ RobustExternalAPICallerService.statsCollector.externalServiceFailed(provider.getApiGroupId(), "Response data was null for some reason");
3747
+ punishProvider(provider);
3748
+ }
3749
+ }
3750
+ var axiosConfig = _extends({}, cancelToken ? {
3751
+ cancelToken: cancelToken
3752
+ } : {}, {
3753
+ timeout: provider.timeout || timeoutMS,
3754
+ headers: (_provider$specificHea = provider.specificHeaders) != null ? _provider$specificHea : {}
3755
+ });
3756
+ var httpMethods = Array.isArray(provider.httpMethod) ? provider.httpMethod : [provider.httpMethod];
3757
+ var iterationsData = [];
3758
+ var _temp11 = _forTo$1(httpMethods, function (subRequestIndex) {
3759
+ function _temp10() {
3760
+ var responsesDataForPages = responsesForPages.map(function (response) {
3761
+ return provider.getDataByResponse(response, parametersValues, subRequestIndex, iterationsData);
3762
+ });
3763
+ var allData = responsesDataForPages;
3764
+ if (Array.isArray(responsesDataForPages[0])) {
3765
+ allData = responsesDataForPages.flat();
3766
+ } else if (responsesDataForPages.length === 1) {
3767
+ allData = responsesDataForPages[0];
3768
+ }
3769
+ iterationsData.push(allData);
3770
+ }
3771
+ var query = provider.composeQueryString(parametersValues, subRequestIndex);
3772
+ var endpoint = "" + provider.endpoint + query;
3773
+ var axiosParams = [endpoint, axiosConfig];
3774
+ if (["post", "put", "patch"].find(function (method) {
3775
+ return method === httpMethods[subRequestIndex];
3776
+ })) {
3777
+ var _provider$composeBody;
3778
+ var body = (_provider$composeBody = provider.composeBody(parametersValues, subRequestIndex)) != null ? _provider$composeBody : null;
3779
+ axiosParams.splice(1, 0, body);
3780
+ }
3781
+ var pageNumber = 0;
3782
+ var responsesForPages = [];
3783
+ var hasNextPage = provider.doesSupportPagination();
3784
+ var _temp9 = _do(function () {
3785
+ function _temp8() {
3786
+ if (hasNextPage) {
3787
+ hasNextPage = !provider.checkWhetherResponseIsForLastPage(responsesForPages[pageNumber - 1], responsesForPages[pageNumber], pageNumber, subRequestIndex);
3788
+ }
3789
+ pageNumber++;
3790
+ }
3791
+ var _temp7 = function () {
3792
+ if (subRequestIndex === 0 && pageNumber === 0) {
3793
+ provider.actualizeLastCalledTimestamp();
3794
+ return Promise.resolve(AxiosAdapter.call.apply(AxiosAdapter, [httpMethods[subRequestIndex]].concat(axiosParams))).then(function (_AxiosAdapter$call) {
3795
+ responsesForPages[pageNumber] = _AxiosAdapter$call;
3796
+ RobustExternalAPICallerService.statsCollector.externalServiceCalledWithoutError(provider.getApiGroupId());
3797
+ });
3798
+ } else {
3799
+ if (pageNumber > 0) {
3800
+ var actualizedParams = provider.changeQueryParametersForPageNumber(parametersValues, responsesForPages[pageNumber - 1], pageNumber, subRequestIndex);
3801
+ var _query = provider.composeQueryString(actualizedParams, subRequestIndex);
3802
+ axiosParams[0] = "" + provider.endpoint + _query;
3803
+ }
3804
+ /**
3805
+ * For second and more request we postpone each request to not exceed RPS
3806
+ * of current provider. We use rpsFactor to dynamically increase the rps to avoid
3807
+ * too frequent calls if we continue failing to retrieve the data due to RPS exceeding.
3808
+ * TODO: [dev] test RPS factor logic (units or integration)
3809
+ */
3810
+
3811
+ var waitingTimeMS = provider.getRps() ? 1000 / (provider.getRps() / rpsFactor) : 0;
3812
+ var postponeUntilRpsExceeded = function postponeUntilRpsExceeded(recursionLevel) {
3813
+ if (recursionLevel === void 0) {
3814
+ recursionLevel = 0;
3815
+ }
3816
+ try {
3817
+ return Promise.resolve(postponeExecution(function () {
3818
+ try {
3819
+ var _temp17 = function _temp17(_result8) {
3820
+ if (_exit) return _result8;
3821
+ provider.actualizeLastCalledTimestamp();
3822
+ return Promise.resolve(AxiosAdapter.call.apply(AxiosAdapter, [httpMethods[subRequestIndex]].concat(axiosParams)));
3823
+ };
3824
+ var _exit;
3825
+ var maxCountOfPostponingAttempts = 2;
3826
+ var _temp16 = function () {
3827
+ if (provider.isRpsExceeded() && recursionLevel < maxCountOfPostponingAttempts) {
3828
+ return Promise.resolve(postponeUntilRpsExceeded(recursionLevel + 1)).then(function (_await$postponeUntilR) {
3829
+ _exit = 1;
3830
+ return _await$postponeUntilR;
3831
+ });
3832
+ }
3833
+ }();
3834
+ return Promise.resolve(_temp16 && _temp16.then ? _temp16.then(_temp17) : _temp17(_temp16));
3835
+ } catch (e) {
3836
+ return Promise.reject(e);
3837
+ }
3838
+ }, waitingTimeMS));
3839
+ } catch (e) {
3840
+ return Promise.reject(e);
3841
+ }
3842
+ };
3843
+ return Promise.resolve(postponeUntilRpsExceeded()).then(function (_postponeUntilRpsExce) {
3844
+ responsesForPages[pageNumber] = _postponeUntilRpsExce;
3845
+ });
3846
+ }
3847
+ }();
3848
+ return _temp7 && _temp7.then ? _temp7.then(_temp8) : _temp8(_temp7);
3849
+ }, function () {
3850
+ return !!hasNextPage;
3851
+ });
3852
+ return _temp9 && _temp9.then ? _temp9.then(_temp10) : _temp10(_temp9);
3853
+ });
3854
+ return _temp11 && _temp11.then ? _temp11.then(_temp12) : _temp12(_temp11);
3855
+ }, function (e) {
3856
+ punishProvider(provider);
3857
+ RobustExternalAPICallerService.statsCollector.externalServiceFailed(provider.getApiGroupId(), e == null ? void 0 : e.message);
3858
+ errors.push(e);
3859
+ });
3860
+ }, function (_wasThrown2, _result7) {
3861
+ providerIndex++;
3862
+ if (_wasThrown2) throw _result7;
3863
+ return _result7;
3864
+ });
3865
+ if (_temp13 && _temp13.then) return _temp13.then(function () {});
3654
3866
  });
3867
+ return Promise.resolve(_temp14 && _temp14.then ? _temp14.then(_temp15) : _temp15(_temp14));
3655
3868
  } catch (e) {
3656
- Logger.logError(e, "getStats");
3869
+ return Promise.reject(e);
3657
3870
  }
3658
3871
  };
3659
- return ExternalServicesStatsCollector;
3872
+ _proto._reorderProvidersByNiceFactor = function _reorderProvidersByNiceFactor() {
3873
+ var providersCopy = [].concat(this.providers);
3874
+ return providersCopy.sort(function (p1, p2) {
3875
+ return p2.niceFactor - p1.niceFactor;
3876
+ });
3877
+ };
3878
+ return RobustExternalAPICallerService;
3660
3879
  }();
3880
+ RobustExternalAPICallerService.statsCollector = new ExternalServicesStatsCollector();
3881
+ RobustExternalAPICallerService.defaultRPSFactor = 1;
3882
+ RobustExternalAPICallerService.rpsMultiplier = 1.05;
3883
+ function punishProvider(provider) {
3884
+ provider.niceFactor = provider.niceFactor - 1;
3885
+ }
3661
3886
 
3662
3887
  /**
3663
- * TODO: [refactoring, critical] update backend copy of this service. Also there is a task to extract this
3664
- * service and other related to it stuff to dedicated npm package task_id=b008ee5e4a3f42c08c73831c4bb3db4e
3888
+ * This util helps to avoid duplicated calls to a shared resource.
3889
+ * It tracks is there currently active calculation for the specific cache id and make all other requests
3890
+ * with the same cache id waiting for this active calculation to be finished. When the calculation ends
3891
+ * the resolver allows all the waiting requesters to get the data from cache and start their own calculations.
3665
3892
  *
3666
- * Template service needed to avoid duplication of the same logic when we need to call
3667
- * external APIs to retrieve some data. The idea is to use several API providers to retrieve the same data. It helps to
3668
- * improve the reliability of a data retrieval.
3669
- */
3670
-
3671
- function _catch$4(body, recover) {
3672
- try {
3673
- var result = body();
3674
- } catch (e) {
3675
- return recover(e);
3676
- }
3677
- if (result && result.then) {
3678
- return result.then(void 0, recover);
3679
- }
3680
- return result;
3681
- }
3893
+ * This class should be instantiated inside some other service where you need to request some resource concurrently.
3894
+ * Rules:
3895
+ * 1. When you need to make a request inside your main service call 'getCachedOrWaitForCachedOrAcquireLock'
3896
+ * on the instance of this class and await for the result. If the flag allowing to start calculation is true
3897
+ * then you can request data inside your main service. Otherwise you should use the cached data as an another
3898
+ * requester just finished the most resent requesting and there is actual data in the cache that
3899
+ * is returned to you here.
3900
+ * 1.1 Also you can acquire a lock directly if you don't want to get cached data. Use the corresponding method 'acquireLock'.
3901
+ *
3902
+ * 2. If you start requesting (when you successfully acquired the lock) then after receiving the result of your
3903
+ * requesting you should call the 'saveCachedData' so the retrieved data will appear in the cache.
3904
+ *
3905
+ * 3. If you successfully acquired the lock then you should after calling the 'saveCachedData' call
3906
+ * the 'releaseLock' - this is mandatory to release the lock and allow other requesters to perform their requests.
3907
+ * WARNING: If for any reason you forget to call this method then this class instance will wait perpetually for
3908
+ * the lock releasing and all your attempts to request the data will constantly fail. So usually call it
3909
+ * inside the 'finally' block.
3910
+ *
3911
+ * TODO: [tests, critical++] add unit tests - massively used logic and can produce sophisticated concurrency bugs
3912
+ */
3913
+
3682
3914
  function _settle$1(pact, state, value) {
3683
3915
  if (!pact.s) {
3684
3916
  if (value instanceof _Pact$1) {
@@ -3698,12 +3930,17 @@ function _settle$1(pact, state, value) {
3698
3930
  }
3699
3931
  pact.s = state;
3700
3932
  pact.v = value;
3701
- var observer = pact.o;
3933
+ const observer = pact.o;
3702
3934
  if (observer) {
3703
3935
  observer(pact);
3704
3936
  }
3705
3937
  }
3706
3938
  }
3939
+
3940
+ /**
3941
+ * Util class to control access to a resource when it can be called in parallel for the same result.
3942
+ * (E.g. getting today coins-fiat rates from some API).
3943
+ */
3707
3944
  var _Pact$1 = /*#__PURE__*/function () {
3708
3945
  function _Pact() {}
3709
3946
  _Pact.prototype.then = function (onFulfilled, onRejected) {
@@ -3766,517 +4003,574 @@ function _for(test, update, body) {
3766
4003
  break;
3767
4004
  }
3768
4005
  }
3769
- if (update) {
3770
- var updateValue = update();
3771
- if (updateValue && updateValue.then && !_isSettledPact$1(updateValue)) {
3772
- stage = 2;
3773
- break;
3774
- }
4006
+ if (update) {
4007
+ var updateValue = update();
4008
+ if (updateValue && updateValue.then && !_isSettledPact$1(updateValue)) {
4009
+ stage = 2;
4010
+ break;
4011
+ }
4012
+ }
4013
+ }
4014
+ var pact = new _Pact$1();
4015
+ var reject = _settle$1.bind(null, pact, 2);
4016
+ (stage === 0 ? shouldContinue.then(_resumeAfterTest) : stage === 1 ? result.then(_resumeAfterBody) : updateValue.then(_resumeAfterUpdate)).then(void 0, reject);
4017
+ return pact;
4018
+ function _resumeAfterBody(value) {
4019
+ result = value;
4020
+ do {
4021
+ if (update) {
4022
+ updateValue = update();
4023
+ if (updateValue && updateValue.then && !_isSettledPact$1(updateValue)) {
4024
+ updateValue.then(_resumeAfterUpdate).then(void 0, reject);
4025
+ return;
4026
+ }
4027
+ }
4028
+ shouldContinue = test();
4029
+ if (!shouldContinue || _isSettledPact$1(shouldContinue) && !shouldContinue.v) {
4030
+ _settle$1(pact, 1, result);
4031
+ return;
4032
+ }
4033
+ if (shouldContinue.then) {
4034
+ shouldContinue.then(_resumeAfterTest).then(void 0, reject);
4035
+ return;
4036
+ }
4037
+ result = body();
4038
+ if (_isSettledPact$1(result)) {
4039
+ result = result.v;
4040
+ }
4041
+ } while (!result || !result.then);
4042
+ result.then(_resumeAfterBody).then(void 0, reject);
4043
+ }
4044
+ function _resumeAfterTest(shouldContinue) {
4045
+ if (shouldContinue) {
4046
+ result = body();
4047
+ if (result && result.then) {
4048
+ result.then(_resumeAfterBody).then(void 0, reject);
4049
+ } else {
4050
+ _resumeAfterBody(result);
4051
+ }
4052
+ } else {
4053
+ _settle$1(pact, 1, result);
4054
+ }
4055
+ }
4056
+ function _resumeAfterUpdate() {
4057
+ if (shouldContinue = test()) {
4058
+ if (shouldContinue.then) {
4059
+ shouldContinue.then(_resumeAfterTest).then(void 0, reject);
4060
+ } else {
4061
+ _resumeAfterTest(shouldContinue);
4062
+ }
4063
+ } else {
4064
+ _settle$1(pact, 1, result);
4065
+ }
4066
+ }
4067
+ }
4068
+ function _catch$5(body, recover) {
4069
+ try {
4070
+ var result = body();
4071
+ } catch (e) {
4072
+ return recover(e);
4073
+ }
4074
+ if (result && result.then) {
4075
+ return result.then(void 0, recover);
4076
+ }
4077
+ return result;
4078
+ }
4079
+ var CacheAndConcurrentRequestsResolver = /*#__PURE__*/function () {
4080
+ /**
4081
+ * @param bio {string} unique identifier for the exact service
4082
+ * @param cache {Cache} cache
4083
+ * @param cacheTtl {number|null} time to live for cache ms. 0 or null means the cache cannot expire
4084
+ * @param [maxCallAttemptsToWaitForAlreadyRunningRequest=100] {number} number of request allowed to do waiting for
4085
+ * result before we fail the original request. Use custom value only if you need to make the attempts count
4086
+ * and polling interval changes.
4087
+ * @param [timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished=1000] {number}
4088
+ * timeout ms for polling for a result. if you change maxCallAttemptsToWaitForAlreadyRunningRequest
4089
+ * then this parameter maybe also require the custom value.
4090
+ * @param [removeExpiredCacheAutomatically=true] {boolean}
4091
+ */
4092
+ function CacheAndConcurrentRequestsResolver(bio, cache, cacheTtl, removeExpiredCacheAutomatically, maxCallAttemptsToWaitForAlreadyRunningRequest, timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished) {
4093
+ if (removeExpiredCacheAutomatically === void 0) {
4094
+ removeExpiredCacheAutomatically = true;
4095
+ }
4096
+ if (maxCallAttemptsToWaitForAlreadyRunningRequest === void 0) {
4097
+ maxCallAttemptsToWaitForAlreadyRunningRequest = 100;
4098
+ }
4099
+ if (timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished === void 0) {
4100
+ timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished = 1000;
4101
+ }
4102
+ if (cacheTtl != null && cacheTtl < timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished * 2) {
4103
+ /*
4104
+ * During the lifetime of this service e.g. if the data is being retrieved slowly we can get
4105
+ * RACE CONDITION when we constantly retrieve data and during retrieval it is expired, so we are trying
4106
+ * to retrieve it again and again.
4107
+ * We have a protection mechanism that we will wait no more than
4108
+ * maxCallAttemptsToWaitForAlreadyRunningRequest * timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished
4109
+ * but this additional check is aimed to reduce potential loading time for some requests.
4110
+ */
4111
+ throw new Error("DEV: Wrong parameters passed to construct " + bio + " - TTL " + cacheTtl + " should be 2 times greater than " + timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished);
4112
+ }
4113
+ this._bio = bio;
4114
+ this._cache = cache;
4115
+ this._cacheTtlMs = cacheTtl != null ? cacheTtl : null;
4116
+ this._maxExecutionTimeMs = maxCallAttemptsToWaitForAlreadyRunningRequest * timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished;
4117
+ this._removeExpiredCacheAutomatically = removeExpiredCacheAutomatically;
4118
+ this._requestsManager = new ManagerOfRequestsToTheSameResource(bio, maxCallAttemptsToWaitForAlreadyRunningRequest, timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished);
4119
+ }
4120
+
4121
+ /**
4122
+ * When using this service this is the major method you should call to get data by cache id.
4123
+ * This method checks is there cached data and ether
4124
+ * - returns you flag that you can start requesting data from the shared resource
4125
+ * - or if there is already started calculation waits until it is finished (removed from this service)
4126
+ * and returns you the retrieved data
4127
+ * - or just returns you the cached data
4128
+ *
4129
+ * 'canStartDataRetrieval' equal true means that the lock was acquired, and you should manually call 'saveCachedData'
4130
+ * if needed and then 'releaseLock' to mark this calculation as finished so other
4131
+ * requesters can take their share of the resource.
4132
+ *
4133
+ * @param cacheId {string}
4134
+ * @return {Promise<({
4135
+ * canStartDataRetrieval: true,
4136
+ * cachedData: any,
4137
+ * lockId: string
4138
+ * }|{
4139
+ * canStartDataRetrieval: false,
4140
+ * cachedData: any
4141
+ * })>}
4142
+ */
4143
+ var _proto = CacheAndConcurrentRequestsResolver.prototype;
4144
+ _proto.getCachedOrWaitForCachedOrAcquireLock = function getCachedOrWaitForCachedOrAcquireLock(cacheId) {
4145
+ try {
4146
+ var _this = this;
4147
+ return Promise.resolve(_catch$5(function () {
4148
+ function _temp2() {
4149
+ var _cached, _cached2;
4150
+ return calculationId ? {
4151
+ canStartDataRetrieval: true,
4152
+ cachedData: (_cached = cached) != null ? _cached : cachedDataBackupIsPresentButExpired,
4153
+ lockId: calculationId
4154
+ } : {
4155
+ canStartDataRetrieval: false,
4156
+ cachedData: (_cached2 = cached) != null ? _cached2 : cachedDataBackupIsPresentButExpired
4157
+ };
4158
+ }
4159
+ var startedAtTimestamp = Date.now();
4160
+ var cached = _this._cache.get(cacheId);
4161
+ var cachedDataBackupIsPresentButExpired = null;
4162
+ if (cached != null && !_this._removeExpiredCacheAutomatically) {
4163
+ var lastUpdateTimestamp = _this._cache.getLastUpdateTimestamp(cacheId);
4164
+ if ((lastUpdateTimestamp != null ? lastUpdateTimestamp : 0) + _this._cacheTtlMs < Date.now()) {
4165
+ /*
4166
+ * Here we are manually clearing 'cached' value retrieved from cache to force the data loading.
4167
+ * But we save its value first to the backup variable to be able to return this value if ongoing
4168
+ * requesting fails.
4169
+ */
4170
+ cachedDataBackupIsPresentButExpired = cached;
4171
+ cached = null;
4172
+ }
4173
+ }
4174
+ var calculationId = null;
4175
+ var isRetrievedCacheExpired = true;
4176
+ var isWaitingForActiveCalculationSucceeded;
4177
+ var weStillHaveSomeTimeToProceedExecution = true;
4178
+ var _temp = _for(function () {
4179
+ return calculationId == null && cached == null && !!isRetrievedCacheExpired && !!weStillHaveSomeTimeToProceedExecution;
4180
+ }, void 0, function () {
4181
+ return Promise.resolve(_this._requestsManager.startCalculationOrWaitForActiveToFinish(cacheId)).then(function (result) {
4182
+ calculationId = typeof result === "string" ? result : null;
4183
+ isWaitingForActiveCalculationSucceeded = typeof result === "boolean" ? result : null;
4184
+ cached = _this._cache.get(cacheId);
4185
+ isRetrievedCacheExpired = isWaitingForActiveCalculationSucceeded && cached == null;
4186
+ weStillHaveSomeTimeToProceedExecution = Date.now() - startedAtTimestamp < _this._maxExecutionTimeMs;
4187
+ });
4188
+ });
4189
+ return _temp && _temp.then ? _temp.then(_temp2) : _temp2(_temp);
4190
+ }, function (e) {
4191
+ improveAndRethrow(e, _this._bio + ".getCachedOrWaitForCachedOrAcquireLock");
4192
+ }));
4193
+ } catch (e) {
4194
+ return Promise.reject(e);
4195
+ }
4196
+ }
4197
+ /**
4198
+ * Returns just the current cache value for the given id.
4199
+ * Doesn't wait for the active calculation, doesn't acquire lock, just retrieves the current cache as it is.
4200
+ *
4201
+ * @param cacheId {string}
4202
+ * @return {any}
4203
+ */
4204
+ ;
4205
+ _proto.getCached = function getCached(cacheId) {
4206
+ try {
4207
+ return this._cache.get(cacheId);
4208
+ } catch (e) {
4209
+ improveAndRethrow(e, "getCached");
4210
+ }
4211
+ };
4212
+ _proto._getTtl = function _getTtl() {
4213
+ return this._removeExpiredCacheAutomatically ? this._cacheTtlMs : null;
4214
+ }
4215
+
4216
+ /**
4217
+ * Directly acquires the lock despite on cached data availability.
4218
+ * So if this method returns result === true you can start the data retrieval.
4219
+ *
4220
+ * @param cacheId {string}
4221
+ * @return {Promise<{ result: true, lockId: string }|{ result: false }>}
4222
+ */;
4223
+ _proto.acquireLock = function acquireLock(cacheId) {
4224
+ try {
4225
+ var _this2 = this;
4226
+ return Promise.resolve(_catch$5(function () {
4227
+ return Promise.resolve(_this2._requestsManager.acquireLock(cacheId));
4228
+ }, function (e) {
4229
+ improveAndRethrow(e, "acquireLock");
4230
+ }));
4231
+ } catch (e) {
4232
+ return Promise.reject(e);
4233
+ }
4234
+ }
4235
+ /**
4236
+ * This method should be called only if you acquired a lock successfully.
4237
+ *
4238
+ * If the current lock id is not equal to the passed one the passed data will be ignored.
4239
+ * Or you can do the synchronous data merging on your side and pass the
4240
+ * wasDataMergedSynchronouslyWithMostRecentCacheState=true so your data will be stored
4241
+ * despite on the lockId.
4242
+ * WARNING: you should do this only if you are sure you perform the synchronous update.
4243
+ *
4244
+ * @param cacheId {string}
4245
+ * @param lockId {string}
4246
+ * @param data {any}
4247
+ * @param [sessionDependentData=true] {boolean}
4248
+ * @param [wasDataMergedSynchronouslyWithMostRecentCacheState=false]
4249
+ */
4250
+ ;
4251
+ _proto.saveCachedData = function saveCachedData(cacheId, lockId, data, sessionDependentData, wasDataMergedSynchronouslyWithMostRecentCacheState) {
4252
+ if (sessionDependentData === void 0) {
4253
+ sessionDependentData = true;
4254
+ }
4255
+ if (wasDataMergedSynchronouslyWithMostRecentCacheState === void 0) {
4256
+ wasDataMergedSynchronouslyWithMostRecentCacheState = false;
3775
4257
  }
3776
- }
3777
- var pact = new _Pact$1();
3778
- var reject = _settle$1.bind(null, pact, 2);
3779
- (stage === 0 ? shouldContinue.then(_resumeAfterTest) : stage === 1 ? result.then(_resumeAfterBody) : updateValue.then(_resumeAfterUpdate)).then(void 0, reject);
3780
- return pact;
3781
- function _resumeAfterBody(value) {
3782
- result = value;
3783
- do {
3784
- if (update) {
3785
- updateValue = update();
3786
- if (updateValue && updateValue.then && !_isSettledPact$1(updateValue)) {
3787
- updateValue.then(_resumeAfterUpdate).then(void 0, reject);
3788
- return;
4258
+ try {
4259
+ if (wasDataMergedSynchronouslyWithMostRecentCacheState || this._requestsManager.isTheLockActiveOne(cacheId, lockId)) {
4260
+ /* We save passed data only if the <caller> has the currently acquired lockId.
4261
+ * If the passed lockId is not the active one it means that other code cleared/stopped the lock
4262
+ * acquired by the <caller> recently due to some urgent/more prior changes.
4263
+ *
4264
+ * But we allow user to pass the 'wasDataMergedSynchronouslyWithMostRecentCacheState' flag
4265
+ * that tells us that the user had taken the most recent cache value and merged his new data
4266
+ * with that cached value (AFTER possibly performing async data retrieval). This means that we
4267
+ * can ignore the fact that his lockId is no more relevant and save the passed data
4268
+ * as it is synchronously merged with the most recent cached data. (Synchronously merged means that
4269
+ * the lost update cannot occur during the merge time as JS execute the synchronous functions\
4270
+ * till the end).
4271
+ */
4272
+ if (sessionDependentData) {
4273
+ this._cache.putSessionDependentData(cacheId, data, this._getTtl());
4274
+ } else {
4275
+ this._cache.put(cacheId, data, this._getTtl());
3789
4276
  }
3790
4277
  }
3791
- shouldContinue = test();
3792
- if (!shouldContinue || _isSettledPact$1(shouldContinue) && !shouldContinue.v) {
3793
- _settle$1(pact, 1, result);
3794
- return;
3795
- }
3796
- if (shouldContinue.then) {
3797
- shouldContinue.then(_resumeAfterTest).then(void 0, reject);
3798
- return;
3799
- }
3800
- result = body();
3801
- if (_isSettledPact$1(result)) {
3802
- result = result.v;
3803
- }
3804
- } while (!result || !result.then);
3805
- result.then(_resumeAfterBody).then(void 0, reject);
3806
- }
3807
- function _resumeAfterTest(shouldContinue) {
3808
- if (shouldContinue) {
3809
- result = body();
3810
- if (result && result.then) {
3811
- result.then(_resumeAfterBody).then(void 0, reject);
3812
- } else {
3813
- _resumeAfterBody(result);
3814
- }
3815
- } else {
3816
- _settle$1(pact, 1, result);
4278
+ } catch (e) {
4279
+ improveAndRethrow(e, this._bio + ".saveCachedData");
3817
4280
  }
3818
4281
  }
3819
- function _resumeAfterUpdate() {
3820
- if (shouldContinue = test()) {
3821
- if (shouldContinue.then) {
3822
- shouldContinue.then(_resumeAfterTest).then(void 0, reject);
3823
- } else {
3824
- _resumeAfterTest(shouldContinue);
4282
+
4283
+ /**
4284
+ * Should be called then and only then if you successfully acquired a lock with the lock id.
4285
+ *
4286
+ * @param cacheId {string}
4287
+ * @param lockId {string}
4288
+ */;
4289
+ _proto.releaseLock = function releaseLock(cacheId, lockId) {
4290
+ try {
4291
+ if (this._requestsManager.isTheLockActiveOne(cacheId, lockId)) {
4292
+ this._requestsManager.finishActiveCalculation(cacheId);
3825
4293
  }
3826
- } else {
3827
- _settle$1(pact, 1, result);
4294
+ } catch (e) {
4295
+ improveAndRethrow(e, this._bio + ".releaseLock");
3828
4296
  }
3829
4297
  }
3830
- }
3831
- function _finallyRethrows$1(body, finalizer) {
3832
- try {
3833
- var result = body();
3834
- } catch (e) {
3835
- return finalizer(true, e);
3836
- }
3837
- if (result && result.then) {
3838
- return result.then(finalizer.bind(null, false), finalizer.bind(null, true));
3839
- }
3840
- return finalizer(false, result);
3841
- }
3842
- function _do(body, test) {
3843
- var awaitBody;
3844
- do {
3845
- var result = body();
3846
- if (result && result.then) {
3847
- if (_isSettledPact$1(result)) {
3848
- result = result.v;
3849
- } else {
3850
- awaitBody = true;
3851
- break;
3852
- }
3853
- }
3854
- var shouldContinue = test();
3855
- if (_isSettledPact$1(shouldContinue)) {
3856
- shouldContinue = shouldContinue.v;
3857
- }
3858
- if (!shouldContinue) {
3859
- return result;
4298
+
4299
+ /**
4300
+ * Actualized currently present cached data by key. Applies the provided function to the cached data.
4301
+ *
4302
+ * @param cacheId {string} id of cache entry
4303
+ * @param synchronousCurrentCacheProcessor (function|null} synchronous function accepting cache entry. Should return
4304
+ * an object in following format:
4305
+ * {
4306
+ * isModified: boolean,
4307
+ * data: any
4308
+ * }
4309
+ * the flag signals whether data was changed during the processing or not
4310
+ * @param [sessionDependent=true] {boolean} whether to mark the cache entry as session-dependent
4311
+ */;
4312
+ _proto.actualizeCachedData = function actualizeCachedData(cacheId, synchronousCurrentCacheProcessor, sessionDependent) {
4313
+ if (sessionDependent === void 0) {
4314
+ sessionDependent = true;
3860
4315
  }
3861
- } while (!shouldContinue.then);
3862
- var pact = new _Pact$1();
3863
- var reject = _settle$1.bind(null, pact, 2);
3864
- (awaitBody ? result.then(_resumeAfterBody) : shouldContinue.then(_resumeAfterTest)).then(void 0, reject);
3865
- return pact;
3866
- function _resumeAfterBody(value) {
3867
- result = value;
3868
- for (;;) {
3869
- shouldContinue = test();
3870
- if (_isSettledPact$1(shouldContinue)) {
3871
- shouldContinue = shouldContinue.v;
3872
- }
3873
- if (!shouldContinue) {
3874
- break;
3875
- }
3876
- if (shouldContinue.then) {
3877
- shouldContinue.then(_resumeAfterTest).then(void 0, reject);
3878
- return;
3879
- }
3880
- result = body();
3881
- if (result && result.then) {
3882
- if (_isSettledPact$1(result)) {
3883
- result = result.v;
4316
+ try {
4317
+ var cached = this._cache.get(cacheId);
4318
+ var result = synchronousCurrentCacheProcessor(cached);
4319
+ if (result != null && result.isModified && (result == null ? void 0 : result.data) != null) {
4320
+ if (sessionDependent) {
4321
+ this._cache.putSessionDependentData(cacheId, result == null ? void 0 : result.data, this._getTtl());
3884
4322
  } else {
3885
- result.then(_resumeAfterBody).then(void 0, reject);
3886
- return;
4323
+ this._cache.put(cacheId, result == null ? void 0 : result.data, this._getTtl());
3887
4324
  }
4325
+
4326
+ /* Here we call the lock releasing to ensure the currently active calculation will be ignored.
4327
+ * This is needed to ensure no 'lost update'.
4328
+ * Lost update can occur if we change data in this method and after that some calculation finishes
4329
+ * having the earlier data as its base to calculate its data set result. And the earlier data
4330
+ * has no changes applied inside this method, so we will lose them.
4331
+ *
4332
+ * This is not so good solution: ideally, we should acquire lock before performing any data updating.
4333
+ * But the goal of this method is to provide an instant ability to update the cached data.
4334
+ * And if we start acquiring the lock here the data update can be postponed significantly.
4335
+ * And this kills the desired nature of this method.
4336
+ * So we better lose some data retrieval (means abusing the resource a bit) than lose
4337
+ * the instant update expected after this method execution.
4338
+ */
4339
+ this._requestsManager.finishActiveCalculation(cacheId);
3888
4340
  }
4341
+ } catch (e) {
4342
+ improveAndRethrow(e, this._bio + ".actualizeCachedData");
4343
+ }
4344
+ };
4345
+ _proto.invalidate = function invalidate(key) {
4346
+ this._cache.invalidate(key);
4347
+ this._requestsManager.finishActiveCalculation(key);
4348
+ };
4349
+ _proto.invalidateContaining = function invalidateContaining(keyPart) {
4350
+ this._cache.invalidateContaining(keyPart);
4351
+ this._requestsManager.finishAllActiveCalculations(keyPart);
4352
+ };
4353
+ _proto.markAsExpiredButDontRemove = function markAsExpiredButDontRemove(key) {
4354
+ if (this._removeExpiredCacheAutomatically) {
4355
+ this._cache.markCacheItemAsExpiredButDontRemove(key, this._cacheTtlMs);
4356
+ } else {
4357
+ this._cache.setLastUpdateTimestamp(key, Date.now() - this._cacheTtlMs - 1);
4358
+ }
4359
+ this._requestsManager.finishAllActiveCalculations(key);
4360
+ };
4361
+ return CacheAndConcurrentRequestsResolver;
4362
+ }();
4363
+ var ManagerOfRequestsToTheSameResource = /*#__PURE__*/function () {
4364
+ /**
4365
+ * @param bio {string} resource-related identifier for logging
4366
+ * @param [maxPollsCount=100] {number} max number of attempts to wait when waiting for a lock acquisition
4367
+ * @param [timeoutDuration=1000] {number} timeout between the polls for a lock acquisition
4368
+ */
4369
+ function ManagerOfRequestsToTheSameResource(bio, maxPollsCount, timeoutDuration) {
4370
+ if (maxPollsCount === void 0) {
4371
+ maxPollsCount = 100;
4372
+ }
4373
+ if (timeoutDuration === void 0) {
4374
+ timeoutDuration = 1000;
3889
4375
  }
3890
- _settle$1(pact, 1, result);
4376
+ this.bio = bio;
4377
+ this.maxPollsCount = maxPollsCount;
4378
+ this.timeoutDuration = timeoutDuration;
4379
+ this._activeCalculationsIds = new Map();
4380
+ this._nextCalculationIds = new Map();
3891
4381
  }
3892
- function _resumeAfterTest(shouldContinue) {
3893
- if (shouldContinue) {
3894
- do {
3895
- result = body();
3896
- if (result && result.then) {
3897
- if (_isSettledPact$1(result)) {
3898
- result = result.v;
3899
- } else {
3900
- result.then(_resumeAfterBody).then(void 0, reject);
3901
- return;
3902
- }
3903
- }
3904
- shouldContinue = test();
3905
- if (_isSettledPact$1(shouldContinue)) {
3906
- shouldContinue = shouldContinue.v;
3907
- }
3908
- if (!shouldContinue) {
3909
- _settle$1(pact, 1, result);
3910
- return;
4382
+
4383
+ /**
4384
+ * If there is no active calculation just creates uuid and returns it.
4385
+ * If there is active calculation waits until it removed from the active calculation uuid variable.
4386
+ *
4387
+ * @param requestHash {string}
4388
+ * @return {Promise<string|boolean>} returns uuid of new active calculation or true if waiting for active
4389
+ * calculation succeed or false if max attempts count exceeded
4390
+ */
4391
+ var _proto2 = ManagerOfRequestsToTheSameResource.prototype;
4392
+ _proto2.startCalculationOrWaitForActiveToFinish = function startCalculationOrWaitForActiveToFinish(requestHash) {
4393
+ try {
4394
+ var _exit;
4395
+ var _this3 = this;
4396
+ var _temp3 = _catch$5(function () {
4397
+ var activeCalculationIdForHash = _this3._activeCalculationsIds.get(requestHash);
4398
+ if (activeCalculationIdForHash == null) {
4399
+ var id = v4();
4400
+ _this3._activeCalculationsIds.set(requestHash, id);
4401
+ _exit = 1;
4402
+ return id;
3911
4403
  }
3912
- } while (!shouldContinue.then);
3913
- shouldContinue.then(_resumeAfterTest).then(void 0, reject);
3914
- } else {
3915
- _settle$1(pact, 1, result);
4404
+ return Promise.resolve(_this3._waitForCalculationIdToFinish(requestHash, activeCalculationIdForHash, 0)).then(function (_await$_this3$_waitFo) {
4405
+ _exit = 1;
4406
+ return _await$_this3$_waitFo;
4407
+ });
4408
+ }, function (e) {
4409
+ Logger.logError(e, "startCalculationOrWaitForActiveToFinish_" + _this3.bio);
4410
+ });
4411
+ return Promise.resolve(_temp3 && _temp3.then ? _temp3.then(function (_result3) {
4412
+ return _exit ? _result3 : null;
4413
+ }) : _exit ? _temp3 : null);
4414
+ } catch (e) {
4415
+ return Promise.reject(e);
3916
4416
  }
3917
4417
  }
3918
- }
3919
- function _forTo$1(array, body, check) {
3920
- var i = -1,
3921
- pact,
3922
- reject;
3923
- function _cycle(result) {
4418
+ /**
4419
+ * Acquires lock to the resource by the provided hash.
4420
+ *
4421
+ * @param requestHash {string}
4422
+ * @return {Promise<{ result: true, lockId: string }|{ result: false }>} result is true if the lock is successfully
4423
+ * acquired, false if the max allowed time to wait for acquisition expired or any unexpected error occurs
4424
+ * during the waiting.
4425
+ */
4426
+ ;
4427
+ _proto2.acquireLock = function acquireLock(requestHash) {
3924
4428
  try {
3925
- while (++i < array.length && (!check || !check())) {
3926
- result = body(i);
3927
- if (result && result.then) {
3928
- if (_isSettledPact$1(result)) {
3929
- result = result.v;
3930
- } else {
3931
- result.then(_cycle, reject || (reject = _settle$1.bind(null, pact = new _Pact$1(), 2)));
3932
- return;
3933
- }
4429
+ var _this4 = this;
4430
+ return Promise.resolve(_catch$5(function () {
4431
+ var _this4$_nextCalculati;
4432
+ var activeId = _this4._activeCalculationsIds.get(requestHash);
4433
+ var nextId = v4();
4434
+ if (activeId == null) {
4435
+ _this4._activeCalculationsIds.set(requestHash, nextId);
4436
+ return {
4437
+ result: true,
4438
+ lockId: nextId
4439
+ };
3934
4440
  }
3935
- }
3936
- if (pact) {
3937
- _settle$1(pact, 1, result);
3938
- } else {
3939
- pact = result;
3940
- }
4441
+ var currentNext = (_this4$_nextCalculati = _this4._nextCalculationIds.get(requestHash)) != null ? _this4$_nextCalculati : [];
4442
+ currentNext.push(nextId);
4443
+ _this4._nextCalculationIds.set(requestHash, currentNext);
4444
+ return Promise.resolve(_this4._waitForCalculationIdToFinish(requestHash, activeId, 0, nextId)).then(function (waitingResult) {
4445
+ return {
4446
+ result: waitingResult,
4447
+ lockId: waitingResult ? nextId : undefined
4448
+ };
4449
+ });
4450
+ }, function (e) {
4451
+ improveAndRethrow(e, "acquireLock");
4452
+ }));
3941
4453
  } catch (e) {
3942
- _settle$1(pact || (pact = new _Pact$1()), 2, e);
4454
+ return Promise.reject(e);
3943
4455
  }
3944
4456
  }
3945
- _cycle();
3946
- return pact;
3947
- }
3948
- var RobustExternalAPICallerService = /*#__PURE__*/function () {
3949
- RobustExternalAPICallerService.getStats = function getStats() {
3950
- this.statsCollector.getStats();
3951
- }
3952
-
3953
- /**
3954
- * @param bio {string} service name for logging
3955
- * @param providersData {ExternalApiProvider[]} array of providers
3956
- * @param [logger] {function} function to be used for logging
3957
- */;
3958
- function RobustExternalAPICallerService(bio, providersData, logger) {
3959
- providersData.forEach(function (provider) {
3960
- if (!provider.endpoint && provider.endpoint !== "" || !provider.httpMethod) {
3961
- throw new Error("Wrong format of providers data for: " + JSON.stringify(provider));
3962
- }
3963
- });
3964
-
3965
- // We add niceFactor - just number to order the providers array by. It is helpful to call
3966
- // less robust APIs only if more robust fails
3967
- this.providers = providersData;
3968
- providersData.forEach(function (provider) {
3969
- return provider.resetNiceFactor();
3970
- });
3971
- this.bio = bio;
3972
- this._logger = Logger.logError;
3973
- }
3974
- var _proto = RobustExternalAPICallerService.prototype;
3975
4457
  /**
3976
- * Performs data retrieval from external APIs. Tries providers till the data is retrieved.
3977
- *
3978
- * @param parametersValues {array} array of values of the parameters for URL query string [and/or body]
3979
- * @param timeoutMS {number} http timeout to wait for response. If provider has its specific timeout value then it is used
3980
- * @param [cancelToken] {object|undefined} axios token to force-cancel requests from high-level code
3981
- * @param [attemptsCount] {number|undefined} number of attempts to be performed
3982
- * @param [doNotFailForNowData] {boolean|undefined} pass true if you do not want us to throw an error if we retrieved null data from all the providers
3983
- * @return {Promise<any>} resolving to retrieved data (or array of results if specific provider requires
3984
- * several requests. NOTE: we flatten nested arrays - results of each separate request done for the specific provider)
3985
- * @throws Error if requests to all providers are failed
4458
+ * Clears active calculation id.
4459
+ * WARNING: if you forget to call this method the start* one will perform maxPollsCount attempts before finishing
4460
+ * @param requestHash {string} hash of request. Helps to distinct the request for the same resource but
4461
+ * having different request parameters and hold a dedicated calculation id per this hash
3986
4462
  */
3987
- _proto.callExternalAPI = function callExternalAPI(parametersValues, timeoutMS, cancelToken, attemptsCount, doNotFailForNowData) {
3988
- if (parametersValues === void 0) {
3989
- parametersValues = [];
3990
- }
3991
- if (timeoutMS === void 0) {
3992
- timeoutMS = 3500;
4463
+ ;
4464
+ _proto2.finishActiveCalculation = function finishActiveCalculation(requestHash) {
4465
+ if (requestHash === void 0) {
4466
+ requestHash = "default";
3993
4467
  }
3994
- if (cancelToken === void 0) {
3995
- cancelToken = null;
4468
+ try {
4469
+ var _this$_nextCalculatio;
4470
+ this._activeCalculationsIds["delete"](requestHash);
4471
+ var next = (_this$_nextCalculatio = this._nextCalculationIds.get(requestHash)) != null ? _this$_nextCalculatio : [];
4472
+ if (next.length) {
4473
+ this._activeCalculationsIds.set(requestHash, next[0]);
4474
+ this._nextCalculationIds.set(requestHash, next.slice(1));
4475
+ }
4476
+ } catch (e) {
4477
+ improveAndRethrow(e, "finishActiveCalculation");
3996
4478
  }
3997
- if (attemptsCount === void 0) {
3998
- attemptsCount = 1;
4479
+ };
4480
+ _proto2.finishAllActiveCalculations = function finishAllActiveCalculations(keyPart) {
4481
+ var _this5 = this;
4482
+ if (keyPart === void 0) {
4483
+ keyPart = "";
3999
4484
  }
4000
- if (doNotFailForNowData === void 0) {
4001
- doNotFailForNowData = false;
4485
+ try {
4486
+ Array.from(this._activeCalculationsIds.keys()).forEach(function (hash) {
4487
+ if (typeof hash === "string" && new RegExp(keyPart).test(hash)) {
4488
+ _this5.finishActiveCalculation(hash);
4489
+ }
4490
+ });
4491
+ } catch (e) {
4492
+ improveAndRethrow(e, "finishAllActiveCalculations");
4002
4493
  }
4494
+ }
4495
+
4496
+ /**
4497
+ * @param requestHash {string}
4498
+ * @param lockId {string}
4499
+ * @return {boolean}
4500
+ */;
4501
+ _proto2.isTheLockActiveOne = function isTheLockActiveOne(requestHash, lockId) {
4003
4502
  try {
4004
- var _this = this;
4005
- var result;
4006
- var calculationUuid = concurrentCalculationsMetadataHolder.startCalculation(_this.bio);
4007
- return Promise.resolve(_finallyRethrows$1(function () {
4008
- return _catch$4(function () {
4009
- function _temp5() {
4010
- var _result2, _result3;
4011
- if (((_result2 = result) == null ? void 0 : _result2.data) == null) {
4012
- // TODO: [feature, moderate] looks like we should not fail for null data as it is strange - the provider will fail when processing data internally
4013
- var error = new Error("Failed to retrieve data. It means all attempts have been failed. DEV: add more attempts to this data retrieval");
4014
- if (!doNotFailForNowData) {
4015
- throw error;
4016
- } else {
4017
- _this._logger(error, _this.bio + ".callExternalAPI");
4018
- }
4019
- }
4020
- return (_result3 = result) == null ? void 0 : _result3.data;
4021
- }
4022
- var i = 0;
4023
- var _temp4 = _for(function () {
4024
- var _result4, _result5;
4025
- return (i < attemptsCount || !!((_result4 = result) != null && _result4.shouldBeForceRetried)) && ((_result5 = result) == null ? void 0 : _result5.data) == null;
4026
- }, function () {
4027
- return ++i;
4028
- }, function () {
4029
- /**
4030
- * We use rpsFactor to improve re-attempting to call the providers if the last attempt resulted with
4031
- * the fail due to abused RPSes of some (most part of) providers.
4032
- * The _performCallAttempt in such a case will return increased rpsFactor inside the result object.
4033
- */
4034
- var rpsFactor = result ? result.rpsFactor : RobustExternalAPICallerService.defaultRPSFactor;
4035
- result = null;
4036
- var _temp3 = _catch$4(function () {
4037
- function _temp2() {
4038
- var _result$errors;
4039
- if ((_result$errors = result.errors) != null && _result$errors.length) {
4040
- var errors = result.errors;
4041
- _this._logger(new Error("Failed at attempt " + i + ". " + errors.length + " errors. Messages: " + safeStringify(errors.map(function (error) {
4042
- return error.message;
4043
- })) + ": " + safeStringify(errors) + "."), _this.bio + ".callExternalAPI", "", true);
4044
- }
4045
- }
4046
- var _temp = function (_result6) {
4047
- if (i === 0 && !((_result6 = result) != null && _result6.shouldBeForceRetried)) {
4048
- return Promise.resolve(_this._performCallAttempt(parametersValues, timeoutMS, cancelToken, rpsFactor, doNotFailForNowData)).then(function (_this$_performCallAtt) {
4049
- result = _this$_performCallAtt;
4050
- });
4051
- } else {
4052
- var maxRps = Math.max.apply(Math, _this.providers.map(function (provider) {
4053
- var _provider$getRps;
4054
- return (_provider$getRps = provider.getRps()) != null ? _provider$getRps : 0;
4055
- }));
4056
- var waitingTimeMs = maxRps ? 1000 / (maxRps / rpsFactor) : 0;
4057
- return Promise.resolve(new Promise(function (resolve, reject) {
4058
- setTimeout(function () {
4059
- try {
4060
- var _temp6 = _catch$4(function () {
4061
- return Promise.resolve(_this._performCallAttempt(parametersValues, timeoutMS, cancelToken, rpsFactor, doNotFailForNowData)).then(function (_this$_performCallAtt2) {
4062
- resolve(_this$_performCallAtt2);
4063
- });
4064
- }, function (e) {
4065
- reject(e);
4066
- });
4067
- return Promise.resolve(_temp6 && _temp6.then ? _temp6.then(function () {}) : void 0);
4068
- } catch (e) {
4069
- return Promise.reject(e);
4070
- }
4071
- }, waitingTimeMs);
4072
- })).then(function (_Promise) {
4073
- result = _Promise;
4074
- });
4075
- }
4076
- }();
4077
- return _temp && _temp.then ? _temp.then(_temp2) : _temp2(_temp);
4078
- }, function (e) {
4079
- _this._logger(e, _this.bio + ".callExternalAPI", "Failed to perform external providers calling");
4080
- });
4081
- if (_temp3 && _temp3.then) return _temp3.then(function () {});
4082
- });
4083
- return _temp4 && _temp4.then ? _temp4.then(_temp5) : _temp5(_temp4);
4084
- }, function (e) {
4085
- improveAndRethrow(e, _this.bio + ".callExternalAPI");
4086
- });
4087
- }, function (_wasThrown, _result) {
4088
- concurrentCalculationsMetadataHolder.endCalculation(_this.bio, calculationUuid);
4089
- if (_wasThrown) throw _result;
4090
- return _result;
4091
- }));
4503
+ return this._activeCalculationsIds.get(requestHash) === lockId;
4092
4504
  } catch (e) {
4093
- return Promise.reject(e);
4505
+ improveAndRethrow(e, "isTheLockActiveOne");
4506
+ }
4507
+ }
4508
+
4509
+ /**
4510
+ * @param requestHash {string}
4511
+ * @param activeCalculationId {string|null}
4512
+ * @param [attemptIndex=0] {number}
4513
+ * @param waitForCalculationId {string|null} if you want to wait for an exact id to appear as active then pass this parameter
4514
+ * @return {Promise<boolean>} true
4515
+ * - if the given calculation id is no more an active one
4516
+ * - or it is equal to waitForCalculationId
4517
+ * false
4518
+ * - if waiting period exceeds the max allowed waiting time or unexpected error occurs
4519
+ * @private
4520
+ */;
4521
+ _proto2._waitForCalculationIdToFinish = function _waitForCalculationIdToFinish(requestHash, activeCalculationId, attemptIndex, waitForCalculationId) {
4522
+ if (attemptIndex === void 0) {
4523
+ attemptIndex = 0;
4524
+ }
4525
+ if (waitForCalculationId === void 0) {
4526
+ waitForCalculationId = null;
4094
4527
  }
4095
- };
4096
- _proto._performCallAttempt = function _performCallAttempt(parametersValues, timeoutMS, cancelToken, rpsFactor, doNotFailForNowData) {
4097
4528
  try {
4098
- var _temp15 = function _temp15() {
4099
- var _data;
4100
- // If we are declining more than 50% of providers (by exceeding RPS) then we note that it better to retry the whole process of providers requesting
4101
- var shouldBeForceRetried = data == null && countOfRequestsDeclinedByRps > Math.floor(providers.length * 0.5);
4102
- var rpsMultiplier = shouldBeForceRetried ? RobustExternalAPICallerService.rpsMultiplier : 1;
4103
- return {
4104
- data: (_data = data) != null ? _data : null,
4105
- shouldBeForceRetried: shouldBeForceRetried,
4106
- rpsFactor: rpsFactor * rpsMultiplier,
4107
- errors: errors
4108
- };
4109
- };
4110
- var _this2 = this;
4111
- var providers = _this2._reorderProvidersByNiceFactor();
4112
- var data = undefined,
4113
- providerIndex = 0,
4114
- countOfRequestsDeclinedByRps = 0,
4115
- errors = [];
4116
- var _temp14 = _for(function () {
4117
- return !data && providerIndex < providers.length;
4118
- }, void 0, function () {
4119
- var provider = providers[providerIndex];
4120
- if (provider.isRpsExceeded()) {
4121
- /**
4122
- * Current provider's RPS is exceeded, so we try next provider. Also, we count such cases to make
4123
- * a decision about the force-retry need.
4124
- */
4125
- ++providerIndex;
4126
- ++countOfRequestsDeclinedByRps;
4127
- return;
4529
+ var _this6 = this;
4530
+ try {
4531
+ if (attemptIndex + 1 > _this6.maxPollsCount) {
4532
+ // Max number of polls for active calculation id change is achieved. So we return false.
4533
+ return Promise.resolve(false);
4128
4534
  }
4129
- var _temp13 = _finallyRethrows$1(function () {
4130
- return _catch$4(function () {
4131
- var _provider$specificHea;
4132
- function _temp12() {
4133
- if (iterationsData.length) {
4134
- if (httpMethods.length > 1) {
4135
- data = provider.incorporateIterationsData(iterationsData);
4136
- } else {
4137
- data = iterationsData[0];
4138
- }
4139
- } else if (!doNotFailForNowData) {
4140
- RobustExternalAPICallerService.statsCollector.externalServiceFailed(provider.getApiGroupId(), "Response data was null for some reason");
4141
- punishProvider(provider);
4142
- }
4143
- }
4144
- var axiosConfig = _extends({}, cancelToken ? {
4145
- cancelToken: cancelToken
4146
- } : {}, {
4147
- timeout: provider.timeout || timeoutMS,
4148
- headers: (_provider$specificHea = provider.specificHeaders) != null ? _provider$specificHea : {}
4149
- });
4150
- var httpMethods = Array.isArray(provider.httpMethod) ? provider.httpMethod : [provider.httpMethod];
4151
- var iterationsData = [];
4152
- var _temp11 = _forTo$1(httpMethods, function (subRequestIndex) {
4153
- function _temp10() {
4154
- var responsesDataForPages = responsesForPages.map(function (response) {
4155
- return provider.getDataByResponse(response, parametersValues, subRequestIndex, iterationsData);
4156
- });
4157
- var allData = responsesDataForPages;
4158
- if (Array.isArray(responsesDataForPages[0])) {
4159
- allData = responsesDataForPages.flat();
4160
- } else if (responsesDataForPages.length === 1) {
4161
- allData = responsesDataForPages[0];
4162
- }
4163
- iterationsData.push(allData);
4164
- }
4165
- var query = provider.composeQueryString(parametersValues, subRequestIndex);
4166
- var endpoint = "" + provider.endpoint + query;
4167
- var axiosParams = [endpoint, axiosConfig];
4168
- if (["post", "put", "patch"].find(function (method) {
4169
- return method === httpMethods[subRequestIndex];
4170
- })) {
4171
- var _provider$composeBody;
4172
- var body = (_provider$composeBody = provider.composeBody(parametersValues, subRequestIndex)) != null ? _provider$composeBody : null;
4173
- axiosParams.splice(1, 0, body);
4535
+ var currentId = _this6._activeCalculationsIds.get(requestHash);
4536
+ if (waitForCalculationId == null ? currentId !== activeCalculationId : currentId === waitForCalculationId) {
4537
+ /* We return true depending on the usage of this function:
4538
+ * 1. if there is calculation id that we should wait for to become an active then we return true only
4539
+ * if this id becomes the active one.
4540
+ *
4541
+ * Theoretically we can fail to wait for the desired calculation id. This can be caused by wrong use of
4542
+ * this service or by any other mistakes/errors. But this waiting function will return false anyway if
4543
+ * the number of polls done exceeds the max allowed.
4544
+ *
4545
+ * 2. if we just wait for the currently active calculation id to be finished then we return true
4546
+ * when we notice that the current active id differs from the original passed into this function.
4547
+ */
4548
+ return Promise.resolve(true);
4549
+ } else {
4550
+ /* The original calculation id is still the active one, so we are scheduling a new attempt to check
4551
+ * whether the active calculation id changed or not in timeoutDuration milliseconds.
4552
+ */
4553
+ var it = _this6;
4554
+ return Promise.resolve(new Promise(function (resolve, reject) {
4555
+ setTimeout(function () {
4556
+ try {
4557
+ resolve(it._waitForCalculationIdToFinish(requestHash, activeCalculationId, attemptIndex + 1));
4558
+ } catch (e) {
4559
+ reject(e);
4174
4560
  }
4175
- var pageNumber = 0;
4176
- var responsesForPages = [];
4177
- var hasNextPage = provider.doesSupportPagination();
4178
- var _temp9 = _do(function () {
4179
- function _temp8() {
4180
- if (hasNextPage) {
4181
- hasNextPage = !provider.checkWhetherResponseIsForLastPage(responsesForPages[pageNumber - 1], responsesForPages[pageNumber], pageNumber, subRequestIndex);
4182
- }
4183
- pageNumber++;
4184
- }
4185
- var _temp7 = function () {
4186
- if (subRequestIndex === 0 && pageNumber === 0) {
4187
- provider.actualizeLastCalledTimestamp();
4188
- return Promise.resolve(AxiosAdapter.call.apply(AxiosAdapter, [httpMethods[subRequestIndex]].concat(axiosParams))).then(function (_AxiosAdapter$call) {
4189
- responsesForPages[pageNumber] = _AxiosAdapter$call;
4190
- RobustExternalAPICallerService.statsCollector.externalServiceCalledWithoutError(provider.getApiGroupId());
4191
- });
4192
- } else {
4193
- if (pageNumber > 0) {
4194
- var actualizedParams = provider.changeQueryParametersForPageNumber(parametersValues, responsesForPages[pageNumber - 1], pageNumber, subRequestIndex);
4195
- var _query = provider.composeQueryString(actualizedParams, subRequestIndex);
4196
- axiosParams[0] = "" + provider.endpoint + _query;
4197
- }
4198
- /**
4199
- * For second and more request we postpone each request to not exceed RPS
4200
- * of current provider. We use rpsFactor to dynamically increase the rps to avoid
4201
- * too frequent calls if we continue failing to retrieve the data due to RPS exceeding.
4202
- * TODO: [dev] test RPS factor logic (units or integration)
4203
- */
4204
-
4205
- var waitingTimeMS = provider.getRps() ? 1000 / (provider.getRps() / rpsFactor) : 0;
4206
- var postponeUntilRpsExceeded = function postponeUntilRpsExceeded(recursionLevel) {
4207
- if (recursionLevel === void 0) {
4208
- recursionLevel = 0;
4209
- }
4210
- try {
4211
- return Promise.resolve(postponeExecution(function () {
4212
- try {
4213
- var _temp17 = function _temp17(_result8) {
4214
- if (_exit) return _result8;
4215
- provider.actualizeLastCalledTimestamp();
4216
- return Promise.resolve(AxiosAdapter.call.apply(AxiosAdapter, [httpMethods[subRequestIndex]].concat(axiosParams)));
4217
- };
4218
- var _exit;
4219
- var maxCountOfPostponingAttempts = 2;
4220
- var _temp16 = function () {
4221
- if (provider.isRpsExceeded() && recursionLevel < maxCountOfPostponingAttempts) {
4222
- return Promise.resolve(postponeUntilRpsExceeded(recursionLevel + 1)).then(function (_await$postponeUntilR) {
4223
- _exit = 1;
4224
- return _await$postponeUntilR;
4225
- });
4226
- }
4227
- }();
4228
- return Promise.resolve(_temp16 && _temp16.then ? _temp16.then(_temp17) : _temp17(_temp16));
4229
- } catch (e) {
4230
- return Promise.reject(e);
4231
- }
4232
- }, waitingTimeMS));
4233
- } catch (e) {
4234
- return Promise.reject(e);
4235
- }
4236
- };
4237
- return Promise.resolve(postponeUntilRpsExceeded()).then(function (_postponeUntilRpsExce) {
4238
- responsesForPages[pageNumber] = _postponeUntilRpsExce;
4239
- });
4240
- }
4241
- }();
4242
- return _temp7 && _temp7.then ? _temp7.then(_temp8) : _temp8(_temp7);
4243
- }, function () {
4244
- return !!hasNextPage;
4245
- });
4246
- return _temp9 && _temp9.then ? _temp9.then(_temp10) : _temp10(_temp9);
4247
- });
4248
- return _temp11 && _temp11.then ? _temp11.then(_temp12) : _temp12(_temp11);
4249
- }, function (e) {
4250
- punishProvider(provider);
4251
- RobustExternalAPICallerService.statsCollector.externalServiceFailed(provider.getApiGroupId(), e == null ? void 0 : e.message);
4252
- errors.push(e);
4253
- });
4254
- }, function (_wasThrown2, _result7) {
4255
- providerIndex++;
4256
- if (_wasThrown2) throw _result7;
4257
- return _result7;
4258
- });
4259
- if (_temp13 && _temp13.then) return _temp13.then(function () {});
4260
- });
4261
- return Promise.resolve(_temp14 && _temp14.then ? _temp14.then(_temp15) : _temp15(_temp14));
4561
+ }, _this6.timeoutDuration);
4562
+ }));
4563
+ }
4564
+ } catch (e) {
4565
+ Logger.logError(e, "_waitForCalculationIdToFinish", "Failed to wait for active calculation id change.");
4566
+ return Promise.resolve(false);
4567
+ }
4262
4568
  } catch (e) {
4263
4569
  return Promise.reject(e);
4264
4570
  }
4265
4571
  };
4266
- _proto._reorderProvidersByNiceFactor = function _reorderProvidersByNiceFactor() {
4267
- var providersCopy = [].concat(this.providers);
4268
- return providersCopy.sort(function (p1, p2) {
4269
- return p2.niceFactor - p1.niceFactor;
4270
- });
4271
- };
4272
- return RobustExternalAPICallerService;
4572
+ return ManagerOfRequestsToTheSameResource;
4273
4573
  }();
4274
- RobustExternalAPICallerService.statsCollector = new ExternalServicesStatsCollector();
4275
- RobustExternalAPICallerService.defaultRPSFactor = 1;
4276
- RobustExternalAPICallerService.rpsMultiplier = 1.05;
4277
- function punishProvider(provider) {
4278
- provider.niceFactor = provider.niceFactor - 1;
4279
- }
4280
4574
 
4281
4575
  /**
4282
4576
  * Extended edit of RobustExternalApiCallerService supporting cache and management of concurrent requests
@@ -4284,7 +4578,7 @@ function punishProvider(provider) {
4284
4578
  * TODO: [tests, critical] Massively used logic
4285
4579
  */
4286
4580
 
4287
- function _catch$3(body, recover) {
4581
+ function _catch$4(body, recover) {
4288
4582
  try {
4289
4583
  var result = body();
4290
4584
  } catch (e) {
@@ -4381,7 +4675,7 @@ var CachedRobustExternalApiCallerService = /*#__PURE__*/function () {
4381
4675
  var cacheId;
4382
4676
  var result;
4383
4677
  return Promise.resolve(_finallyRethrows(function () {
4384
- return _catch$3(function () {
4678
+ return _catch$4(function () {
4385
4679
  cacheId = _this._calculateCacheId(parametersValues, customHashFunctionForParams);
4386
4680
  return Promise.resolve(_this._cahceAndRequestsResolver.getCachedOrWaitForCachedOrAcquireLock(cacheId)).then(function (_this$_cahceAndReques) {
4387
4681
  var _result2, _result4;
@@ -4441,14 +4735,111 @@ var CachedRobustExternalApiCallerService = /*#__PURE__*/function () {
4441
4735
  customHashFunctionForParams = null;
4442
4736
  }
4443
4737
  try {
4444
- var hash = typeof customHashFunctionForParams === "function" ? customHashFunctionForParams(parametersValues) : !parametersValues ? "" : new Hashes.SHA512().hex(safeStringify(parametersValues));
4445
- return this._provider.bio + "-" + hash;
4738
+ var hash = typeof customHashFunctionForParams === "function" ? customHashFunctionForParams(parametersValues) : !parametersValues ? "" : new Hashes.SHA512().hex(safeStringify(parametersValues));
4739
+ return this._provider.bio + "-" + hash;
4740
+ } catch (e) {
4741
+ improveAndRethrow(e, this._provider.bio + "_calculateCacheId");
4742
+ }
4743
+ };
4744
+ return CachedRobustExternalApiCallerService;
4745
+ }();
4746
+
4747
+ function _catch$3(body, recover) {
4748
+ try {
4749
+ var result = body();
4750
+ } catch (e) {
4751
+ return recover(e);
4752
+ }
4753
+ if (result && result.then) {
4754
+ return result.then(void 0, recover);
4755
+ }
4756
+ return result;
4757
+ }
4758
+ var BigdatacloudIpAddressProvider = /*#__PURE__*/function (_ExternalApiProvider) {
4759
+ _inheritsLoose(BigdatacloudIpAddressProvider, _ExternalApiProvider);
4760
+ function BigdatacloudIpAddressProvider() {
4761
+ return _ExternalApiProvider.call(this, "https://api.bigdatacloud.net/data/client-ip", "get", 15000, ApiGroups.BIGDATACLOUD) || this;
4762
+ }
4763
+ var _proto = BigdatacloudIpAddressProvider.prototype;
4764
+ _proto.getDataByResponse = function getDataByResponse(response, params, subRequestIndex, iterationsData) {
4765
+ var _response$data;
4766
+ return (response == null ? void 0 : response.data) && ((_response$data = response.data) == null ? void 0 : _response$data.ipString);
4767
+ };
4768
+ return BigdatacloudIpAddressProvider;
4769
+ }(ExternalApiProvider);
4770
+ var TrackipIpAddressProvider = /*#__PURE__*/function (_ExternalApiProvider2) {
4771
+ _inheritsLoose(TrackipIpAddressProvider, _ExternalApiProvider2);
4772
+ function TrackipIpAddressProvider() {
4773
+ return _ExternalApiProvider2.call(this, "https://www.trackip.net/ip", "get", 15000, ApiGroups.TRACKIP) || this;
4774
+ }
4775
+ var _proto2 = TrackipIpAddressProvider.prototype;
4776
+ _proto2.getDataByResponse = function getDataByResponse(response, params, subRequestIndex, iterationsData) {
4777
+ return response == null ? void 0 : response.data;
4778
+ };
4779
+ return TrackipIpAddressProvider;
4780
+ }(ExternalApiProvider);
4781
+ var IpifyV6IpAddressProvider = /*#__PURE__*/function (_ExternalApiProvider3) {
4782
+ _inheritsLoose(IpifyV6IpAddressProvider, _ExternalApiProvider3);
4783
+ function IpifyV6IpAddressProvider() {
4784
+ return _ExternalApiProvider3.call(this, "https://api6.ipify.org/?format=json", "get", 15000, ApiGroups.IPIFY) || this;
4785
+ }
4786
+ var _proto3 = IpifyV6IpAddressProvider.prototype;
4787
+ _proto3.getDataByResponse = function getDataByResponse(response, params, subRequestIndex, iterationsData) {
4788
+ var _response$data2;
4789
+ return (response == null ? void 0 : response.data) && ((_response$data2 = response.data) == null ? void 0 : _response$data2.ip);
4790
+ };
4791
+ return IpifyV6IpAddressProvider;
4792
+ }(ExternalApiProvider);
4793
+ var IpifyIpAddressProvider = /*#__PURE__*/function (_ExternalApiProvider4) {
4794
+ _inheritsLoose(IpifyIpAddressProvider, _ExternalApiProvider4);
4795
+ function IpifyIpAddressProvider() {
4796
+ return _ExternalApiProvider4.call(this, "https://api.ipify.org/?format=json", "get", 15000, ApiGroups.IPIFY) || this;
4797
+ }
4798
+ var _proto4 = IpifyIpAddressProvider.prototype;
4799
+ _proto4.getDataByResponse = function getDataByResponse(response, params, subRequestIndex, iterationsData) {
4800
+ var _response$data3;
4801
+ return (response == null ? void 0 : response.data) && ((_response$data3 = response.data) == null ? void 0 : _response$data3.ip);
4802
+ };
4803
+ return IpifyIpAddressProvider;
4804
+ }(ExternalApiProvider);
4805
+ var WhatismyipaddressIpAddressProvider = /*#__PURE__*/function (_ExternalApiProvider5) {
4806
+ _inheritsLoose(WhatismyipaddressIpAddressProvider, _ExternalApiProvider5);
4807
+ function WhatismyipaddressIpAddressProvider() {
4808
+ return _ExternalApiProvider5.call(this, "http://bot.whatismyipaddress.com/", "get", 15000, ApiGroups.WHATISMYIPADDRESS) || this;
4809
+ }
4810
+ var _proto5 = WhatismyipaddressIpAddressProvider.prototype;
4811
+ _proto5.getDataByResponse = function getDataByResponse(response, params, subRequestIndex, iterationsData) {
4812
+ return response == null ? void 0 : response.data;
4813
+ };
4814
+ return WhatismyipaddressIpAddressProvider;
4815
+ }(ExternalApiProvider);
4816
+ var IpAddressProvider = /*#__PURE__*/function () {
4817
+ function IpAddressProvider() {}
4818
+ /**
4819
+ * Returns current public IP address identified by one of external services.
4820
+ *
4821
+ * It is easier than manual identification and also (as ip needed for server side to check it) it saves us from
4822
+ * issues related to changes of infrastructure configurations (like adding proxies etc.) so we should not configure
4823
+ * anything on server side to get correct client's IP.
4824
+ *
4825
+ * @returns {Promise<String>} IP address
4826
+ * @throws {Error} if fails to retrieve IP address from all the services
4827
+ */
4828
+ IpAddressProvider.getClientIpAddress = function getClientIpAddress() {
4829
+ try {
4830
+ var _this = this;
4831
+ return Promise.resolve(_catch$3(function () {
4832
+ return Promise.resolve(_this.externalIPAddressAPICaller.callExternalAPICached([], 7000));
4833
+ }, function (e) {
4834
+ improveAndRethrow(e, "getClientIpAddress");
4835
+ }));
4446
4836
  } catch (e) {
4447
- improveAndRethrow(e, this._provider.bio + "_calculateCacheId");
4837
+ return Promise.reject(e);
4448
4838
  }
4449
4839
  };
4450
- return CachedRobustExternalApiCallerService;
4840
+ return IpAddressProvider;
4451
4841
  }();
4842
+ IpAddressProvider.externalIPAddressAPICaller = new CachedRobustExternalApiCallerService("externalIPAddressAPICaller", new Cache(EventBusInstance), [new BigdatacloudIpAddressProvider(), new TrackipIpAddressProvider(), new IpifyV6IpAddressProvider(), new IpifyIpAddressProvider(), new WhatismyipaddressIpAddressProvider()], 300000);
4452
4843
 
4453
4844
  /**
4454
4845
  * Utils class needed to perform cancelling of axios request inside some process.
@@ -4476,239 +4867,6 @@ var CancelProcessing = /*#__PURE__*/function () {
4476
4867
  return CancelProcessing;
4477
4868
  }();
4478
4869
 
4479
- var ExternalApiProvider = /*#__PURE__*/function () {
4480
- /**
4481
- * Creates an instance of external api provider.
4482
- *
4483
- * If you need sub-request then use 'subRequestIndex' to check current request index in functions below.
4484
- * Also use array for 'httpMethod'.
4485
- *
4486
- * If the endpoint of dedicated provider has pagination then you should customize the behavior using
4487
- * "changeQueryParametersForPageNumber", "checkWhetherResponseIsForLastPage".
4488
- *
4489
- * We perform RPS counting all over the App to avoid blocking our clients due to abuses of the providers.
4490
- *
4491
- * @param endpoint {string} URL to the provider's endpoint. Note: you can customize it using composeQueryString
4492
- * @param [httpMethod] {string|string[]} one of "get", "post", "put", "patch", "delete" or an array of these values
4493
- * for request having sub-requests
4494
- * @param [timeout] {number} number of milliseconds to wait for the response
4495
- * @param [apiGroup] {ApiGroup} singleton object containing parameters of API group. Helpful when you use the same
4496
- * api for different providers to avoid hardcoding RPS inside each provider what can cause mistakes
4497
- * @param [specificHeaders] {Object} contains specific keys (headers) and values (their content) if needed for this provider
4498
- * @param [maxPageLength] {number} optional number of items per page if the request supports pagination
4499
- */
4500
- function ExternalApiProvider(endpoint, httpMethod, timeout, apiGroup, specificHeaders, maxPageLength) {
4501
- var _maxPageLength, _specificHeaders;
4502
- if (specificHeaders === void 0) {
4503
- specificHeaders = {};
4504
- }
4505
- if (maxPageLength === void 0) {
4506
- maxPageLength = Number.MAX_SAFE_INTEGER;
4507
- }
4508
- this.endpoint = endpoint;
4509
- this.httpMethod = httpMethod != null ? httpMethod : "get";
4510
- // TODO: [refactoring, critical] We have two timeouts for robust data retrieval - here and inside the service method call, need to remain the only
4511
- this.timeout = timeout != null ? timeout : 10000;
4512
- // TODO: [refactoring, critical] We need single place for all RPSes as we use them as hardcoded constants now inside different services
4513
- this.apiGroup = apiGroup;
4514
- this.maxPageLength = (_maxPageLength = maxPageLength) != null ? _maxPageLength : Number.MAX_SAFE_INTEGER;
4515
- this.niceFactor = 1;
4516
- this.specificHeaders = (_specificHeaders = specificHeaders) != null ? _specificHeaders : {};
4517
- }
4518
- var _proto = ExternalApiProvider.prototype;
4519
- _proto.getRps = function getRps() {
4520
- var _this$apiGroup$rps;
4521
- return (_this$apiGroup$rps = this.apiGroup.rps) != null ? _this$apiGroup$rps : 2;
4522
- };
4523
- _proto.isRpsExceeded = function isRpsExceeded() {
4524
- return this.apiGroup.isRpsExceeded();
4525
- };
4526
- _proto.actualizeLastCalledTimestamp = function actualizeLastCalledTimestamp() {
4527
- this.apiGroup.actualizeLastCalledTimestamp();
4528
- };
4529
- _proto.getApiGroupId = function getApiGroupId() {
4530
- return this.apiGroup.id;
4531
- }
4532
-
4533
- /**
4534
- * Some endpoint can require several sub requests. Example is one request to get confirmed transactions
4535
- * and another request for unconfirmed transactions. You should override this method to return true for such requests.
4536
- *
4537
- * @return {boolean} true if this provider requires several requests to retrieve the data
4538
- */;
4539
- _proto.doesRequireSubRequests = function doesRequireSubRequests() {
4540
- return false;
4541
- }
4542
-
4543
- /**
4544
- * Some endpoint support pagination. Override this method if so and implement corresponding methods.
4545
- *
4546
- * @return {boolean} true if this provider requires several requests to retrieve the data
4547
- */;
4548
- _proto.doesSupportPagination = function doesSupportPagination() {
4549
- return false;
4550
- }
4551
-
4552
- /**
4553
- * Composes a query string to be added to the endpoint of this provider.
4554
- *
4555
- * @param params {any[]} params array passed to the RobustExternalAPICallerService
4556
- * @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
4557
- * @returns {string} query string to be concatenated with endpoint
4558
- */;
4559
- _proto.composeQueryString = function composeQueryString(params, subRequestIndex) {
4560
- return "";
4561
- }
4562
-
4563
- /**
4564
- * Composes a body to be added to the request
4565
- *
4566
- * @param params {any[]} params array passed to the RobustExternalAPICallerService
4567
- * @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
4568
- * @returns {string}
4569
- */;
4570
- _proto.composeBody = function composeBody(params, subRequestIndex) {
4571
- return "";
4572
- }
4573
-
4574
- /**
4575
- * Extracts data from the response and returns it
4576
- *
4577
- * @param response {Object} HTTP response returned by provider
4578
- * @param [params] {any[]} params array passed to the RobustExternalAPICallerService
4579
- * @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
4580
- * @param iterationsData {any[]} array of data retrieved from previous sub-requests
4581
- * @returns {any}
4582
- */;
4583
- _proto.getDataByResponse = function getDataByResponse(response, params, subRequestIndex, iterationsData) {
4584
- return [];
4585
- }
4586
-
4587
- /**
4588
- * Function changing the query string according to page number and previous response
4589
- * Only for endpoints supporting pagination
4590
- *
4591
- * @param params {any[]} params array passed to the RobustExternalAPICallerService
4592
- * @param previousResponse {Object} HTTP response returned by provider for previous call (previous page)
4593
- * @param pageNumber {number} new page number. We count from 0. You need to manually increment with 1 if your
4594
- * provider counts pages starting with 1
4595
- * @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
4596
- * @returns {any[]}
4597
- */;
4598
- _proto.changeQueryParametersForPageNumber = function changeQueryParametersForPageNumber(params, previousResponse, pageNumber, subRequestIndex) {
4599
- return params;
4600
- }
4601
-
4602
- /**
4603
- * Function checking whether the response is for the last page to stop requesting for a next page.
4604
- * Only for endpoints supporting pagination.
4605
- *
4606
- * @param previousResponse {Object} HTTP response returned by provider for previous call (previous page)
4607
- * @param currentResponse {Object} HTTP response returned by provider for current call (current page, next after the previous)
4608
- * @param currentPageNumber {number} current page number (for current response)
4609
- * @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
4610
- * @returns {boolean}
4611
- */;
4612
- _proto.checkWhetherResponseIsForLastPage = function checkWhetherResponseIsForLastPage(previousResponse, currentResponse, currentPageNumber, subRequestIndex) {
4613
- return true;
4614
- }
4615
-
4616
- /**
4617
- * Resets the nice factor to default value
4618
- */;
4619
- _proto.resetNiceFactor = function resetNiceFactor() {
4620
- this.niceFactor = 1;
4621
- }
4622
-
4623
- /**
4624
- * Internal method used for requests requiring sub-requests.
4625
- *
4626
- * @param iterationsData {any[]} iterations data retrieved from getDataByResponse called per sub-request.
4627
- * @return {any} by default flatten the passed iterations data array. Should be redefined if you need another logic.
4628
- */;
4629
- _proto.incorporateIterationsData = function incorporateIterationsData(iterationsData) {
4630
- return iterationsData.flat();
4631
- };
4632
- return ExternalApiProvider;
4633
- }();
4634
-
4635
- /**
4636
- * Models a group of APIs provided by the same owner and used for different services in our app.
4637
- * It means we need to mention RPS several times for each usage and also have some holder of last call timestamp per
4638
- * api group. So this concept allows to use it for exact ExternalApiProvider and make sure that you use the same
4639
- * RPS value and make decisions on base of the same timestamp of last call to the API group owner.
4640
- */
4641
- var ApiGroup = /*#__PURE__*/function () {
4642
- function ApiGroup(id, rps, backendProxyIdGenerator) {
4643
- if (backendProxyIdGenerator === void 0) {
4644
- backendProxyIdGenerator = null;
4645
- }
4646
- this.id = id;
4647
- this.rps = rps;
4648
- this.lastCalledTimestamp = null;
4649
- this.backendProxyIdGenerator = backendProxyIdGenerator;
4650
- }
4651
- var _proto = ApiGroup.prototype;
4652
- _proto.isRpsExceeded = function isRpsExceeded() {
4653
- var _this$lastCalledTimes;
4654
- return ((_this$lastCalledTimes = this.lastCalledTimestamp) != null ? _this$lastCalledTimes : 0) + Math.floor(1000 / this.rps) > Date.now();
4655
- };
4656
- _proto.actualizeLastCalledTimestamp = function actualizeLastCalledTimestamp() {
4657
- this.lastCalledTimestamp = Date.now();
4658
- };
4659
- return ApiGroup;
4660
- }();
4661
- var ApiGroups = {
4662
- /**
4663
- * Currently we use free version of etherscan provider with 0.2 RPS. But we have API key with 100k requests free
4664
- * per month. So we can add it if not enough current RPS.
4665
- */
4666
- ETHERSCAN: new ApiGroup("etherscan", 0.17),
4667
- // Actually 0.2 but fails sometime, so we use smaller
4668
- ALCHEMY: new ApiGroup("alchemy", 0.3, function (networkKey) {
4669
- return "alchemy-" + networkKey;
4670
- }),
4671
- BLOCKSTREAM: new ApiGroup("blockstream", 0.2),
4672
- BLOCKCHAIN_INFO: new ApiGroup("blockchain.info", 1),
4673
- BLOCKNATIVE: new ApiGroup("blocknative", 0.5),
4674
- ETHGASSTATION: new ApiGroup("ethgasstation", 0.5),
4675
- TRONGRID: new ApiGroup("trongrid", 0.3, function (networkKey) {
4676
- return "trongrid-" + networkKey;
4677
- }),
4678
- TRONSCAN: new ApiGroup("tronscan", 0.3),
4679
- GETBLOCK: new ApiGroup("getblock", 0.3),
4680
- COINCAP: new ApiGroup("coincap", 0.5),
4681
- // 200 per minute without API key
4682
- COINGECKO: new ApiGroup("coingecko", 0.9),
4683
- // actually 0.13-0.5 according to the docs but we use smaller due to expirienced frequent abuses
4684
- MESSARI: new ApiGroup("messari", 0.2),
4685
- BTCCOM: new ApiGroup("btccom", 0.2),
4686
- BITAPS: new ApiGroup("bitaps", 0.25),
4687
- // Docs say that RPS is 3 but using it causes frequent 429 HTTP errors
4688
- CEX: new ApiGroup("cex", 0.5),
4689
- // Just assumption for RPS
4690
- BIGDATACLOUD: new ApiGroup("bigdatacloud", 1),
4691
- // Just assumption for RPS
4692
- TRACKIP: new ApiGroup("trackip", 1),
4693
- // Just assumption for RPS
4694
- IPIFY: new ApiGroup("ipify", 1),
4695
- // Just assumption for RPS
4696
- WHATISMYIPADDRESS: new ApiGroup("whatismyipaddress", 1),
4697
- // Just assumption for RPS
4698
- EXCHANGERATE: new ApiGroup("exchangerate", 1),
4699
- // Just assumption for RPS
4700
- FRANKFURTER: new ApiGroup("frankfurter", 1),
4701
- // Just assumption for RPS
4702
- BITGO: new ApiGroup("bitgo", 1),
4703
- // Just assumption for RPS
4704
- BITCOINER: new ApiGroup("bitcoiner", 1),
4705
- // Just assumption for RPS
4706
- BITCORE: new ApiGroup("bitcore", 1),
4707
- // Just assumption for RPS
4708
- // BLOCKCHAIR: new ApiGroup("blockchair", 0.04), // this provider require API key for commercial use (10usd 10000 reqs), we will add it later
4709
- MEMPOOL: new ApiGroup("mempool", 0.2) // Just assumption for RPS
4710
- };
4711
-
4712
4870
  var ExistingSwap =
4713
4871
  /**
4714
4872
  * @param swapId {string}
@@ -4717,6 +4875,7 @@ var ExistingSwap =
4717
4875
  * @param expiresAt {number}
4718
4876
  * @param confirmations {number}
4719
4877
  * @param rate {string}
4878
+ * @param fixed {boolean}
4720
4879
  * @param refundAddress {string}
4721
4880
  * @param payToAddress {string}
4722
4881
  * @param fromCoin {Coin}
@@ -4733,7 +4892,7 @@ var ExistingSwap =
4733
4892
  * @param toExtraId {string}
4734
4893
  * @param refundExtraId {string}
4735
4894
  */
4736
- function ExistingSwap(swapId, status, createdAt, expiresAt, confirmations, rate, refundAddress, payToAddress, fromCoin, fromAmount, fromTransactionId, fromTransactionLink, toCoin, toAmount, toTransactionId, toTransactionLink, toAddress,
4895
+ function ExistingSwap(swapId, status, createdAt, expiresAt, confirmations, rate, fixed, refundAddress, payToAddress, fromCoin, fromAmount, fromTransactionId, fromTransactionLink, toCoin, toAmount, toTransactionId, toTransactionLink, toAddress,
4737
4896
  // TODO: [refactoring, moderate] toAddress is not quite clear. How about recipientAddress? task_id=0815a111c99543b78d374217eadbde4f
4738
4897
  partner, fromExtraId, toExtraId, refundExtraId) {
4739
4898
  this.swapId = swapId;
@@ -4742,6 +4901,7 @@ partner, fromExtraId, toExtraId, refundExtraId) {
4742
4901
  this.expiresAt = expiresAt;
4743
4902
  this.confirmations = confirmations;
4744
4903
  this.rate = rate;
4904
+ this.fixed = fixed;
4745
4905
  this.refundAddress = refundAddress;
4746
4906
  this.payToAddress = payToAddress;
4747
4907
  this.fromCoin = fromCoin;
@@ -4768,6 +4928,7 @@ var ExistingSwapWithFiatData = /*#__PURE__*/function (_ExistingSwap) {
4768
4928
  * @param expiresAt {number}
4769
4929
  * @param confirmations {number}
4770
4930
  * @param rate {string}
4931
+ * @param fixed {boolean}
4771
4932
  * @param refundAddress {string}
4772
4933
  * @param payToAddress {string}
4773
4934
  * @param fromCoin {Coin}
@@ -4788,9 +4949,9 @@ var ExistingSwapWithFiatData = /*#__PURE__*/function (_ExistingSwap) {
4788
4949
  * @param fiatCurrencyCode {string}
4789
4950
  * @param fiatCurrencyDecimals {number}
4790
4951
  */
4791
- function ExistingSwapWithFiatData(swapId, status, createdAt, expiresAt, confirmations, rate, refundAddress, payToAddress, fromCoin, fromAmount, fromTransactionId, fromTransactionLink, toCoin, toAmount, toTransactionId, toTransactionLink, toAddress, partner, fromExtraId, toExtraId, refundExtraId, fromAmountFiat, toAmountFiat, fiatCurrencyCode, fiatCurrencyDecimals) {
4952
+ function ExistingSwapWithFiatData(swapId, status, createdAt, expiresAt, confirmations, rate, fixed, refundAddress, payToAddress, fromCoin, fromAmount, fromTransactionId, fromTransactionLink, toCoin, toAmount, toTransactionId, toTransactionLink, toAddress, partner, fromExtraId, toExtraId, refundExtraId, fromAmountFiat, toAmountFiat, fiatCurrencyCode, fiatCurrencyDecimals) {
4792
4953
  var _this;
4793
- _this = _ExistingSwap.call(this, swapId, status, createdAt, expiresAt, confirmations, rate, refundAddress, payToAddress, fromCoin, fromAmount, fromTransactionId, fromTransactionLink, toCoin, toAmount, toTransactionId, toTransactionLink, toAddress, partner, fromExtraId, toExtraId, refundExtraId) || this;
4954
+ _this = _ExistingSwap.call(this, swapId, status, createdAt, expiresAt, confirmations, rate, fixed, refundAddress, payToAddress, fromCoin, fromAmount, fromTransactionId, fromTransactionLink, toCoin, toAmount, toTransactionId, toTransactionLink, toAddress, partner, fromExtraId, toExtraId, refundExtraId) || this;
4794
4955
  _this.fromAmountFiat = fromAmountFiat;
4795
4956
  _this.toAmountFiat = toAmountFiat;
4796
4957
  _this.fiatCurrencyCode = fiatCurrencyCode;
@@ -4807,7 +4968,7 @@ var ExistingSwapWithFiatData = /*#__PURE__*/function (_ExistingSwap) {
4807
4968
  * @return {ExistingSwapWithFiatData}
4808
4969
  */
4809
4970
  ExistingSwapWithFiatData.fromExistingSwap = function fromExistingSwap(existingSwap, fromAmountFiat, toAmountFiat, fiatCurrencyCode, fiatCurrencyDecimals) {
4810
- return new ExistingSwapWithFiatData(existingSwap.swapId, existingSwap.status, existingSwap.createdAt, existingSwap.expiresAt, existingSwap.confirmations, existingSwap.rate, existingSwap.refundAddress, existingSwap.payToAddress, existingSwap.fromCoin, existingSwap.fromAmount, existingSwap.fromTransactionId, existingSwap.fromTransactionLink, existingSwap.toCoin, existingSwap.toAmount, existingSwap.toTransactionId, existingSwap.toTransactionLink, existingSwap.toAddress, existingSwap.partner, existingSwap.fromExtraId, existingSwap.toExtraId, existingSwap.refundExtraId, fromAmountFiat, toAmountFiat, fiatCurrencyCode, fiatCurrencyDecimals);
4971
+ return new ExistingSwapWithFiatData(existingSwap.swapId, existingSwap.status, existingSwap.createdAt, existingSwap.expiresAt, existingSwap.confirmations, existingSwap.rate, existingSwap.fixed, existingSwap.refundAddress, existingSwap.payToAddress, existingSwap.fromCoin, existingSwap.fromAmount, existingSwap.fromTransactionId, existingSwap.fromTransactionLink, existingSwap.toCoin, existingSwap.toAmount, existingSwap.toTransactionId, existingSwap.toTransactionLink, existingSwap.toAddress, existingSwap.partner, existingSwap.fromExtraId, existingSwap.toExtraId, existingSwap.refundExtraId, fromAmountFiat, toAmountFiat, fiatCurrencyCode, fiatCurrencyDecimals);
4811
4972
  };
4812
4973
  return ExistingSwapWithFiatData;
4813
4974
  }(ExistingSwap);
@@ -4825,8 +4986,9 @@ var BaseSwapCreationInfo =
4825
4986
  * @param max {string}
4826
4987
  * @param fiatMax {number}
4827
4988
  * @param durationMinutesRange {string}
4989
+ * @param fixed {boolean}
4828
4990
  */
4829
- function BaseSwapCreationInfo(fromCoin, toCoin, fromAmountCoins, toAmountCoins, rate, rawSwapData, min, fiatMin, max, fiatMax, durationMinutesRange) {
4991
+ function BaseSwapCreationInfo(fromCoin, toCoin, fromAmountCoins, toAmountCoins, rate, rawSwapData, min, fiatMin, max, fiatMax, durationMinutesRange, fixed) {
4830
4992
  this.fromCoin = fromCoin;
4831
4993
  this.toCoin = toCoin;
4832
4994
  this.fromAmountCoins = fromAmountCoins;
@@ -4838,6 +5000,7 @@ function BaseSwapCreationInfo(fromCoin, toCoin, fromAmountCoins, toAmountCoins,
4838
5000
  this.max = max;
4839
5001
  this.fiatMax = fiatMax;
4840
5002
  this.durationMinutesRange = durationMinutesRange;
5003
+ this.fixed = fixed;
4841
5004
  };
4842
5005
 
4843
5006
  var SwapProvider = /*#__PURE__*/function () {
@@ -4936,6 +5099,7 @@ var SwapProvider = /*#__PURE__*/function () {
4936
5099
  * @param fromCoin {Coin}
4937
5100
  * @param toCoin {Coin}
4938
5101
  * @param amountCoins {string}
5102
+ * @param [fixed=false] {boolean|null} null means fixed or float doesn't matter
4939
5103
  * @param [fromCoinToUsdRate=null] pass if you want to increase the min amount returned
4940
5104
  * by provider with some fixed "insurance" amount to cover min amount fluctuations.
4941
5105
  * @return {Promise<({
@@ -4951,11 +5115,12 @@ var SwapProvider = /*#__PURE__*/function () {
4951
5115
  * greatestMax: (string|null),
4952
5116
  * rate: (string|null),
4953
5117
  * durationMinutesRange: string,
5118
+ * fixed: boolean,
4954
5119
  * [rawSwapData]: Object
4955
5120
  * })>}
4956
5121
  */
4957
5122
  ;
4958
- _proto.getSwapInfo = function getSwapInfo(fromCoin, toCoin, amountCoins, fromCoinToUsdRate) {
5123
+ _proto.getSwapInfo = function getSwapInfo(fromCoin, toCoin, amountCoins, fixed, fromCoinToUsdRate) {
4959
5124
  try {
4960
5125
  throw new Error("Not implemented in base");
4961
5126
  } catch (e) {
@@ -4963,7 +5128,7 @@ var SwapProvider = /*#__PURE__*/function () {
4963
5128
  }
4964
5129
  }
4965
5130
  /**
4966
- * For fail result we return one of SwapProvider.CREATION_FAIL_REASONS or SwapProvider.COMMON_ERRORS.
5131
+ * For fail result we return one of SwapProvider.CREATION_FAIL_REASONS or SwapProvider.COMMON_ERRORS.*
4967
5132
  *
4968
5133
  * @param fromCoin {Coin}
4969
5134
  * @param toCoin {Coin}
@@ -4972,6 +5137,7 @@ var SwapProvider = /*#__PURE__*/function () {
4972
5137
  * @param refundAddress {string}
4973
5138
  * @param rawSwapData {Object|null}
4974
5139
  * @param clientIpAddress {string}
5140
+ * @param fixed {boolean}
4975
5141
  * @param [toCurrencyExtraId=""] {string} optional extra ID
4976
5142
  * @param [refundExtraId=""] {string} optional extra ID for refund address
4977
5143
  * @return {Promise<({
@@ -4984,7 +5150,8 @@ var SwapProvider = /*#__PURE__*/function () {
4984
5150
  * toAmount: string,
4985
5151
  * toAddress: string,
4986
5152
  * rate: string,
4987
- * fromCurrencyExtraId: string|undefined
5153
+ * fromCurrencyExtraId: string|undefined,
5154
+ * fixed: boolean
4988
5155
  * }|{
4989
5156
  * result: false,
4990
5157
  * reason: string,
@@ -4992,7 +5159,7 @@ var SwapProvider = /*#__PURE__*/function () {
4992
5159
  * })>}
4993
5160
  */
4994
5161
  ;
4995
- _proto.createSwap = function createSwap(fromCoin, toCoin, amount, toAddress, refundAddress, rawSwapData, clientIpAddress, toCurrencyExtraId, refundExtraId) {
5162
+ _proto.createSwap = function createSwap(fromCoin, toCoin, amount, toAddress, refundAddress, rawSwapData, clientIpAddress, fixed, toCurrencyExtraId, refundExtraId) {
4996
5163
  try {
4997
5164
  throw new Error("Not implemented in base");
4998
5165
  } catch (e) {
@@ -5047,7 +5214,9 @@ SwapProvider.COMMON_ERRORS = {
5047
5214
  SwapProvider.NO_SWAPS_REASONS = {
5048
5215
  TOO_LOW: "tooLow",
5049
5216
  TOO_HIGH: "tooHigh",
5050
- NOT_SUPPORTED: "notSupported"
5217
+ NOT_SUPPORTED: "notSupported",
5218
+ NO_FIXED_BUT_HAVE_FLOATING: "noFixedButHaveFloating",
5219
+ NO_FLOATING_BUT_HAVE_FIXED: "noFloatingButHaveFixed"
5051
5220
  };
5052
5221
  SwapProvider.CREATION_FAIL_REASONS = {
5053
5222
  RETRIABLE_FAIL: "retriableFail"
@@ -5259,7 +5428,6 @@ var SwapspaceSwapProvider = /*#__PURE__*/function (_SwapProvider) {
5259
5428
  var ticker = "" + code + (code === network ? "" : network);
5260
5429
  var defaultDecimalPlacesForCoinNotSupportedOOB = 8;
5261
5430
  var defaultMinConfirmationsForCoinNotSupportedOOB = 1;
5262
- // TODO: [dev] maybe we should recognize standard protocols?
5263
5431
  coin = new Coin(item.name, ticker, code, defaultDecimalPlacesForCoinNotSupportedOOB, null, "", null, null, defaultMinConfirmationsForCoinNotSupportedOOB, null, [], 60000, null,
5264
5432
  // We cannot recognize blockchain from swapspace data
5265
5433
  code !== network ? new Protocol(network) : null, item.contractAddress || null, false);
@@ -5272,7 +5440,7 @@ var SwapspaceSwapProvider = /*#__PURE__*/function (_SwapProvider) {
5272
5440
  network: item.network,
5273
5441
  hasExtraId: item.hasExtraId,
5274
5442
  extraIdName: item.extraIdName,
5275
- isPopular: !!(item != null && item.popular),
5443
+ isPopular: !!item.popular,
5276
5444
  iconURL: item.icon ? "https://storage.swapspace.co" + item.icon : FALLBACK_ICON_URL,
5277
5445
  deposit: (_item$deposit = item.deposit) != null ? _item$deposit : false,
5278
5446
  withdrawal: (_item$withdrawal = item.withdrawal) != null ? _item$withdrawal : false,
@@ -5330,7 +5498,7 @@ var SwapspaceSwapProvider = /*#__PURE__*/function (_SwapProvider) {
5330
5498
  };
5331
5499
  }
5332
5500
  Logger.log("Loading USDT->coin rate as not found in cache:", coin == null ? void 0 : coin.ticker);
5333
- return Promise.resolve(_this7.getSwapInfo(usdtTrc20, coin, "5000")).then(function (result) {
5501
+ return Promise.resolve(_this7.getSwapInfo(usdtTrc20, coin, "5000", false)).then(function (result) {
5334
5502
  if (!result.result) {
5335
5503
  return {
5336
5504
  result: false
@@ -5338,7 +5506,7 @@ var SwapspaceSwapProvider = /*#__PURE__*/function (_SwapProvider) {
5338
5506
  }
5339
5507
 
5340
5508
  // This calculation is not precise as we cannot recognize the actual fee and network fee. Just approximate.
5341
- var standardSwapspaceFeeMultiplier = 1.004; // usually 0.2%
5509
+ var standardSwapspaceFeeMultiplier = 1.004; // fee is usually 0.4%
5342
5510
  var rate = BigNumber(1).div(BigNumber(result.rate).times(standardSwapspaceFeeMultiplier)).toString();
5343
5511
  _this7._cache.put("swapspace_usdt_rate_" + coin.ticker, rate, 15 * 60000 // 15 minutes
5344
5512
  );
@@ -5366,7 +5534,10 @@ var SwapspaceSwapProvider = /*#__PURE__*/function (_SwapProvider) {
5366
5534
  improveAndRethrow(e, "getCoinByTickerIfPresent");
5367
5535
  }
5368
5536
  };
5369
- _proto.getSwapInfo = function getSwapInfo(fromCoin, toCoin, amountCoins, fromCoinToUsdRate) {
5537
+ _proto.getSwapInfo = function getSwapInfo(fromCoin, toCoin, amountCoins, fixed, fromCoinToUsdRate) {
5538
+ if (fixed === void 0) {
5539
+ fixed = false;
5540
+ }
5370
5541
  if (fromCoinToUsdRate === void 0) {
5371
5542
  fromCoinToUsdRate = null;
5372
5543
  }
@@ -5374,8 +5545,8 @@ var SwapspaceSwapProvider = /*#__PURE__*/function (_SwapProvider) {
5374
5545
  var _this8 = this;
5375
5546
  var loggerSource = "getSwapInfo";
5376
5547
  return Promise.resolve(_catch$2(function () {
5377
- if (!(fromCoin instanceof Coin) || !(toCoin instanceof Coin) || typeof amountCoins !== "string" || BigNumber(amountCoins).lt("0")) {
5378
- throw new Error("Wrong input params: " + amountCoins + " " + fromCoin.ticker + " -> " + toCoin.ticker + (fromCoin instanceof Coin) + (toCoin instanceof Coin));
5548
+ if (!(fromCoin instanceof Coin) || !(toCoin instanceof Coin) || typeof amountCoins !== "string" || BigNumber(amountCoins).lt("0") || fixed !== null && typeof fixed !== "boolean") {
5549
+ throw new Error("Wrong input params: " + amountCoins + " " + fromCoin.ticker + " -> " + toCoin.ticker + ", " + (fromCoin instanceof Coin) + ", " + (toCoin instanceof Coin) + ", " + typeof fixed + " " + fixed);
5379
5550
  }
5380
5551
  var fromCoinSwapspaceDetails = _this8._supportedCoins.find(function (i) {
5381
5552
  var _i$coin;
@@ -5391,17 +5562,29 @@ var SwapspaceSwapProvider = /*#__PURE__*/function (_SwapProvider) {
5391
5562
  return !fromCoinSwapspaceDetails.deposit || !toCoinSwapspaceDetails.withdrawal ? {
5392
5563
  result: false,
5393
5564
  reason: SwapProvider.NO_SWAPS_REASONS.NOT_SUPPORTED
5394
- } : Promise.resolve(axios.get(_this8._URL + "/api/v2/amounts?fromCurrency=" + fromCoinSwapspaceDetails.code + "&fromNetwork=" + fromCoinSwapspaceDetails.network + "&toNetwork=" + toCoinSwapspaceDetails.network + "&toCurrency=" + toCoinSwapspaceDetails.code + "&amount=" + amountCoins + "&float=true&estimated=false")).then(function (response) {
5395
- var _response$data;
5565
+ } : Promise.resolve(axios.get(_this8._URL + "/api/v2/amounts?fromCurrency=" + fromCoinSwapspaceDetails.code + "&fromNetwork=" + fromCoinSwapspaceDetails.network + "&toNetwork=" + toCoinSwapspaceDetails.network + "&toCurrency=" + toCoinSwapspaceDetails.code + "&amount=" + amountCoins + "&estimated=false")).then(function (response) {
5566
+ var _response$data, _exchangesSupportingT;
5396
5567
  Logger.log("Retrieved " + (response == null || (_response$data = response.data) == null ? void 0 : _response$data.length) + " options", loggerSource);
5397
5568
  var options = Array.isArray(response.data) ? response.data : [];
5398
- var exchangesSupportingThePair = options.filter(function (exchange) {
5569
+ var exchangesSupportingThePairDespiteFixedOrFloating = options.filter(function (exchange) {
5399
5570
  return (exchange == null ? void 0 : exchange.exists) && !BANNED_PARTNERS.find(function (bannedPartner) {
5400
5571
  return bannedPartner === (exchange == null ? void 0 : exchange.partner);
5401
- }) && (exchange == null ? void 0 : exchange.fixed) === false && (exchange.min === 0 || exchange.max === 0 || exchange.max > exchange.min || (typeof exchange.min !== "number" || typeof exchange.max !== "number") && exchange.toAmount > 0);
5572
+ }) && ((exchange == null ? void 0 : exchange.fixed) === false || (exchange == null ? void 0 : exchange.fixed) === true) && (exchange.min === 0 || exchange.max === 0 || exchange.max > exchange.min || (typeof exchange.min !== "number" || typeof exchange.max !== "number") && exchange.toAmount > 0);
5402
5573
  });
5403
- Logger.log((exchangesSupportingThePair == null ? void 0 : exchangesSupportingThePair.length) + " of them have exist=true", loggerSource);
5404
- if (!exchangesSupportingThePair.length) {
5574
+ var exchangesSupportingThePair = exchangesSupportingThePairDespiteFixedOrFloating;
5575
+ if (fixed != null) {
5576
+ exchangesSupportingThePair = exchangesSupportingThePairDespiteFixedOrFloating.filter(function (option) {
5577
+ return option.fixed === fixed;
5578
+ });
5579
+ }
5580
+ Logger.log(((_exchangesSupportingT = exchangesSupportingThePair) == null ? void 0 : _exchangesSupportingT.length) + " of them have exist=true", loggerSource);
5581
+ if (exchangesSupportingThePair.length === 0) {
5582
+ if (exchangesSupportingThePairDespiteFixedOrFloating.length > 0 && fixed !== null) {
5583
+ return {
5584
+ result: false,
5585
+ reason: fixed ? SwapProvider.NO_SWAPS_REASONS.NO_FIXED_BUT_HAVE_FLOATING : SwapProvider.NO_SWAPS_REASONS.NO_FLOATING_BUT_HAVE_FIXED
5586
+ };
5587
+ }
5405
5588
  return {
5406
5589
  result: false,
5407
5590
  reason: SwapProvider.NO_SWAPS_REASONS.NOT_SUPPORTED
@@ -5472,6 +5655,7 @@ var SwapspaceSwapProvider = /*#__PURE__*/function (_SwapProvider) {
5472
5655
  greatestMax: greatestMax,
5473
5656
  rate: rate != null ? AmountUtils.trim(rate, _this8._maxRateDigits) : null,
5474
5657
  durationMinutesRange: (_bestOpt$duration = bestOpt.duration) != null ? _bestOpt$duration : null,
5658
+ fixed: bestOpt.fixed,
5475
5659
  rawSwapData: bestOpt
5476
5660
  };
5477
5661
  }
@@ -5499,7 +5683,7 @@ var SwapspaceSwapProvider = /*#__PURE__*/function (_SwapProvider) {
5499
5683
  return Promise.reject(e);
5500
5684
  }
5501
5685
  };
5502
- _proto.createSwap = function createSwap(fromCoin, toCoin, amount, toAddress, refundAddress, rawSwapData, clientIpAddress, toCurrencyExtraId, refundExtraId) {
5686
+ _proto.createSwap = function createSwap(fromCoin, toCoin, amount, toAddress, refundAddress, rawSwapData, clientIpAddress, fixed, toCurrencyExtraId, refundExtraId) {
5503
5687
  if (toCurrencyExtraId === void 0) {
5504
5688
  toCurrencyExtraId = "";
5505
5689
  }
@@ -5511,13 +5695,18 @@ var SwapspaceSwapProvider = /*#__PURE__*/function (_SwapProvider) {
5511
5695
  var loggerSource = "createSwap";
5512
5696
  var partner = rawSwapData == null ? void 0 : rawSwapData.partner;
5513
5697
  return Promise.resolve(_catch$2(function () {
5514
- if (!(fromCoin instanceof Coin) || !(toCoin instanceof Coin) || typeof amount !== "string" || typeof toAddress !== "string" || typeof refundAddress !== "string") {
5515
- throw new Error("Invalid input: " + fromCoin + " " + toCoin + " " + amount + " " + toAddress + " " + refundAddress);
5698
+ if (!(fromCoin instanceof Coin) || !(toCoin instanceof Coin) || typeof amount !== "string" || typeof toAddress !== "string" || typeof refundAddress !== "string" || typeof clientIpAddress != "string" || typeof fixed != "boolean" || clientIpAddress.length === 0) {
5699
+ throw new Error("Invalid input: " + fromCoin + " " + toCoin + " " + amount + " " + toAddress + " " + refundAddress + " " + (clientIpAddress == null ? void 0 : clientIpAddress.length) + " " + fixed);
5516
5700
  }
5517
5701
  if (typeof partner !== "string" || typeof (rawSwapData == null ? void 0 : rawSwapData.fromCurrency) !== "string" || typeof (rawSwapData == null ? void 0 : rawSwapData.fromNetwork) !== "string" || typeof (rawSwapData == null ? void 0 : rawSwapData.toCurrency) !== "string" || typeof (rawSwapData == null ? void 0 : rawSwapData.toNetwork) !== "string" || typeof (rawSwapData == null ? void 0 : rawSwapData.id) !== "string" // can be just empty
5518
5702
  ) {
5519
5703
  throw new Error("Invalid raw swap data: " + safeStringify(rawSwapData));
5520
5704
  }
5705
+ var _this9$_supportedCoin = _this9._supportedCoins.reduce(function (prev, coinData) {
5706
+ return [coinData.coin.ticker === fromCoin.ticker ? coinData.hasExtraId : prev[0], coinData.coin.ticker === toCoin.ticker ? coinData.hasExtraId : prev[1]];
5707
+ }, [false, false]),
5708
+ fromCurrencyHasExtraId = _this9$_supportedCoin[0],
5709
+ toCurrencyHasExtraId = _this9$_supportedCoin[1];
5521
5710
  return Promise.resolve(_this9._fetchSupportedCurrenciesIfNeeded()).then(function () {
5522
5711
  var _toCurrencyExtraId, _refundExtraId;
5523
5712
  var requestData = {
@@ -5528,10 +5717,10 @@ var SwapspaceSwapProvider = /*#__PURE__*/function (_SwapProvider) {
5528
5717
  toNetwork: rawSwapData == null ? void 0 : rawSwapData.toNetwork,
5529
5718
  address: toAddress,
5530
5719
  amount: amount,
5531
- fixed: false,
5532
- extraId: (_toCurrencyExtraId = toCurrencyExtraId) != null ? _toCurrencyExtraId : "",
5533
- refundExtraId: (_refundExtraId = refundExtraId) != null ? _refundExtraId : "",
5720
+ fixed: fixed,
5721
+ extraId: toCurrencyHasExtraId ? (_toCurrencyExtraId = toCurrencyExtraId) != null ? _toCurrencyExtraId : "" : "",
5534
5722
  // This param is not documented. But the refund is usually manual so this is not critical.
5723
+ refundExtraId: fromCurrencyHasExtraId ? (_refundExtraId = refundExtraId) != null ? _refundExtraId : "" : "",
5535
5724
  rateId: rawSwapData == null ? void 0 : rawSwapData.id,
5536
5725
  userIp: clientIpAddress,
5537
5726
  refund: refundAddress
@@ -5542,7 +5731,7 @@ var SwapspaceSwapProvider = /*#__PURE__*/function (_SwapProvider) {
5542
5731
  Logger.log("Creation result " + safeStringify(result), loggerSource);
5543
5732
  if (result != null && result.id) {
5544
5733
  var _result$from, _result$from2, _result$to, _result$to2, _result$from4, _result$from5, _result$to4, _result$to5, _result$from$extraId, _result$from6;
5545
- if (typeof (result == null || (_result$from = result.from) == null ? void 0 : _result$from.amount) !== "number" || typeof (result == null || (_result$from2 = result.from) == null ? void 0 : _result$from2.address) !== "string" || typeof (result == null || (_result$to = result.to) == null ? void 0 : _result$to.amount) !== "number" || typeof (result == null || (_result$to2 = result.to) == null ? void 0 : _result$to2.address) !== "string") throw new Error("Wrong swap creation result " + result);
5734
+ if (typeof (result == null || (_result$from = result.from) == null ? void 0 : _result$from.amount) !== "number" || typeof (result == null || (_result$from2 = result.from) == null ? void 0 : _result$from2.address) !== "string" || typeof (result == null ? void 0 : result.fixed) !== "boolean" || typeof (result == null || (_result$to = result.to) == null ? void 0 : _result$to.amount) !== "number" || typeof (result == null || (_result$to2 = result.to) == null ? void 0 : _result$to2.address) !== "string") throw new Error("Wrong swap creation result " + result);
5546
5735
  /* We use the returned rate preferably but if the retrieved
5547
5736
  * rate 0/null/undefined we calculate it manually */
5548
5737
  var rate = result.rate;
@@ -5562,7 +5751,8 @@ var SwapspaceSwapProvider = /*#__PURE__*/function (_SwapProvider) {
5562
5751
  toAmount: AmountUtils.trim(result == null || (_result$to4 = result.to) == null ? void 0 : _result$to4.amount, toCoin.digits),
5563
5752
  toAddress: result == null || (_result$to5 = result.to) == null ? void 0 : _result$to5.address,
5564
5753
  fromCurrencyExtraId: (_result$from$extraId = result == null || (_result$from6 = result.from) == null ? void 0 : _result$from6.extraId) != null ? _result$from$extraId : "",
5565
- rate: AmountUtils.trim(rate, _this9._maxRateDigits)
5754
+ rate: AmountUtils.trim(rate, _this9._maxRateDigits),
5755
+ fixed: result.fixed
5566
5756
  };
5567
5757
  }
5568
5758
  var errorMessage = "Swap creation succeeded but the response is wrong: " + safeStringify(response);
@@ -5679,7 +5869,7 @@ var SwapspaceSwapProvider = /*#__PURE__*/function (_SwapProvider) {
5679
5869
  var status = _this10._mapSwapspaceStatusToRabbitStatus(swap.status, isExpiredByTime);
5680
5870
  var toDigits = status === SwapProvider.SWAP_STATUSES.REFUNDED ? fromCoin.digits : toCoin.digits;
5681
5871
  var addressToSendCoinsToSwapspace = swap.from.address;
5682
- return new ExistingSwap(swapIds[index], status, toUtcTimestamp(swap.timestamps.createdAt), expiresAt, swap.confirmations, AmountUtils.trim(swap.rate, _this10._maxRateDigits), swap.refundAddress, addressToSendCoinsToSwapspace, fromCoin, AmountUtils.trim(swap.from.amount, fromCoin.digits), swap.from.transactionHash, swap.blockExplorerTransactionUrl.from, toCoin, AmountUtils.trim(swap.to.amount, toDigits), swap.to.transactionHash, swap.blockExplorerTransactionUrl.to, swap.to.address, swap.partner, (_swap$from$extraId = swap.from.extraId) != null ? _swap$from$extraId : null, (_swap$to$extraId = swap.to.extraId) != null ? _swap$to$extraId : null, (_swap$refundExtraId = swap.refundExtraId) != null ? _swap$refundExtraId : null);
5872
+ return new ExistingSwap(swapIds[index], status, toUtcTimestamp(swap.timestamps.createdAt), expiresAt, swap.confirmations, AmountUtils.trim(swap.rate, _this10._maxRateDigits), swap.fixed, swap.refundAddress, addressToSendCoinsToSwapspace, fromCoin, AmountUtils.trim(swap.from.amount, fromCoin.digits), swap.from.transactionHash, swap.blockExplorerTransactionUrl.from, toCoin, AmountUtils.trim(swap.to.amount, toDigits), swap.to.transactionHash, swap.blockExplorerTransactionUrl.to, swap.to.address, swap.partner, (_swap$from$extraId = swap.from.extraId) != null ? _swap$from$extraId : null, (_swap$to$extraId = swap.to.extraId) != null ? _swap$to$extraId : null, (_swap$refundExtraId = swap.refundExtraId) != null ? _swap$refundExtraId : null);
5683
5873
  }).flat();
5684
5874
  Logger.log("Swap details result " + safeStringify(swaps), loggerSource);
5685
5875
  return {
@@ -6237,6 +6427,7 @@ var PublicSwapService = /*#__PURE__*/function () {
6237
6427
  * @param fromCoin {Coin}
6238
6428
  * @param toCoin {Coin}
6239
6429
  * @param fromAmountCoins {string}
6430
+ * @param [fixed=false] {boolean|null} null means fixed or float doesn't matter
6240
6431
  * @param [withoutFiat=false] {boolean} pass true if you don't need the fiat equivalent - this will diminish requests count
6241
6432
  * @return {Promise<{
6242
6433
  * result: false,
@@ -6252,7 +6443,10 @@ var PublicSwapService = /*#__PURE__*/function () {
6252
6443
  * }>}
6253
6444
  */
6254
6445
  ;
6255
- _proto.getPublicSwapDetails = function getPublicSwapDetails(fromCoin, toCoin, fromAmountCoins, withoutFiat) {
6446
+ _proto.getPublicSwapDetails = function getPublicSwapDetails(fromCoin, toCoin, fromAmountCoins, fixed, withoutFiat) {
6447
+ if (fixed === void 0) {
6448
+ fixed = false;
6449
+ }
6256
6450
  if (withoutFiat === void 0) {
6257
6451
  withoutFiat = false;
6258
6452
  }
@@ -6263,7 +6457,7 @@ var PublicSwapService = /*#__PURE__*/function () {
6263
6457
  function _temp2(_this7$_swapProvider$) {
6264
6458
  var _this7$_swapProvider$2;
6265
6459
  var coinUsdtRate = withoutFiat ? _this7$_swapProvider$ : (_this7$_swapProvider$2 = _this7$_swapProvider$ == null ? void 0 : _this7$_swapProvider$.rate) != null ? _this7$_swapProvider$2 : null;
6266
- return Promise.resolve(_this7._swapProvider.getSwapInfo(fromCoin, toCoin, fromAmountCoins, coinUsdtRate)).then(function (details) {
6460
+ return Promise.resolve(_this7._swapProvider.getSwapInfo(fromCoin, toCoin, fromAmountCoins, fixed, coinUsdtRate)).then(function (details) {
6267
6461
  var _result$swapCreationI, _result$swapCreationI2;
6268
6462
  var min = details.result ? details.min : details.smallestMin;
6269
6463
  var max = details.result ? details.max : details.greatestMax;
@@ -6290,9 +6484,15 @@ var PublicSwapService = /*#__PURE__*/function () {
6290
6484
  };
6291
6485
  };
6292
6486
  if (!details.result) {
6293
- if ((details == null ? void 0 : details.reason) === SwapProvider.NO_SWAPS_REASONS.NOT_SUPPORTED) return composeFailResult(PublicSwapService.PUBLIC_SWAP_DETAILS_FAIL_REASONS.PAIR_NOT_SUPPORTED);else if ((details == null ? void 0 : details.reason) === SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED) {
6487
+ if ((details == null ? void 0 : details.reason) === SwapProvider.NO_SWAPS_REASONS.NOT_SUPPORTED) {
6488
+ return composeFailResult(PublicSwapService.PUBLIC_SWAP_DETAILS_FAIL_REASONS.PAIR_NOT_SUPPORTED);
6489
+ } else if ((details == null ? void 0 : details.reason) === SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED) {
6294
6490
  SwapUtils.safeHandleRequestsLimitExceeding();
6295
6491
  return composeFailResult(PublicSwapService.PUBLIC_SWAPS_COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED);
6492
+ } else if ((details == null ? void 0 : details.reason) === SwapProvider.NO_SWAPS_REASONS.NO_FLOATING_BUT_HAVE_FIXED) {
6493
+ return composeFailResult(PublicSwapService.PUBLIC_SWAP_DETAILS_FAIL_REASONS.NO_FLOATING_BUT_HAVE_FIXED_PUBLIC_SWAP_OPTION);
6494
+ } else if ((details == null ? void 0 : details.reason) === SwapProvider.NO_SWAPS_REASONS.NO_FIXED_BUT_HAVE_FLOATING) {
6495
+ return composeFailResult(PublicSwapService.PUBLIC_SWAP_DETAILS_FAIL_REASONS.NO_FIXED_BUT_HAVE_FLOATING_PUBLIC_SWAP_OPTION);
6296
6496
  }
6297
6497
  }
6298
6498
  var fromAmountBigNumber = BigNumber(fromAmountCoins);
@@ -6304,7 +6504,7 @@ var PublicSwapService = /*#__PURE__*/function () {
6304
6504
  var toAmountCoins = AmountUtils.trim(fromAmountBigNumber.times(details.rate), fromCoin.digits);
6305
6505
  var result = {
6306
6506
  result: true,
6307
- swapCreationInfo: new BaseSwapCreationInfo(fromCoin, toCoin, fromAmountCoins, toAmountCoins, details.rate, details.rawSwapData, min, fiatMin, max, fiatMax, details.durationMinutesRange)
6507
+ swapCreationInfo: new BaseSwapCreationInfo(fromCoin, toCoin, fromAmountCoins, toAmountCoins, details.rate, details.rawSwapData, min, fiatMin, max, fiatMax, details.durationMinutesRange, details.fixed)
6308
6508
  };
6309
6509
  Logger.log("Result: " + safeStringify({
6310
6510
  result: result.result,
@@ -6350,7 +6550,8 @@ var PublicSwapService = /*#__PURE__*/function () {
6350
6550
  * fromCoin: Coin,
6351
6551
  * rate: string,
6352
6552
  * swapId: string,
6353
- * fromCurrencyExtraId: string
6553
+ * fromCurrencyExtraId: string,
6554
+ * fixed: boolean
6354
6555
  * }|{
6355
6556
  * result: false,
6356
6557
  * reason: string
@@ -6370,7 +6571,7 @@ var PublicSwapService = /*#__PURE__*/function () {
6370
6571
  fromCoin: swapCreationInfo == null || (_swapCreationInfo$fro = swapCreationInfo.fromCoin) == null ? void 0 : _swapCreationInfo$fro.ticker,
6371
6572
  toCoin: swapCreationInfo == null || (_swapCreationInfo$toC = swapCreationInfo.toCoin) == null ? void 0 : _swapCreationInfo$toC.ticker
6372
6573
  })), loggerSource);
6373
- return Promise.resolve(_this8._swapProvider.createSwap(fromCoin, toCoin, fromAmount, toAddress, refundAddress, swapCreationInfo.rawSwapData, clientIp, toCurrencyExtraId, refundExtraId)).then(function (result) {
6574
+ return Promise.resolve(_this8._swapProvider.createSwap(fromCoin, toCoin, fromAmount, toAddress, refundAddress, swapCreationInfo.rawSwapData, clientIp, swapCreationInfo.fixed, toCurrencyExtraId, refundExtraId)).then(function (result) {
6374
6575
  var _exit;
6375
6576
  function _temp6(_result8) {
6376
6577
  if (_exit) return _result8;
@@ -6400,6 +6601,7 @@ var PublicSwapService = /*#__PURE__*/function () {
6400
6601
  if (result.result && result != null && result.swapId) {
6401
6602
  var _temp4 = function _temp4() {
6402
6603
  var _result$fromCurrencyE;
6604
+ // TODO: feature, cirtical] add GA event. task_id=tbd
6403
6605
  EventBusInstance.dispatch(PublicSwapService.PUBLIC_SWAP_CREATED_EVENT, null, fromCoin.ticker, toCoin.ticker, _fromAmountFiat);
6404
6606
  var toReturn = {
6405
6607
  result: true,
@@ -6416,7 +6618,9 @@ var PublicSwapService = /*#__PURE__*/function () {
6416
6618
  durationMinutesRange: swapCreationInfo.durationMinutesRange,
6417
6619
  address: result.fromAddress,
6418
6620
  // CRITICAL: this is the address to send coins to swaps provider
6419
- fromCurrencyExtraId: (_result$fromCurrencyE = result.fromCurrencyExtraId) != null ? _result$fromCurrencyE : "" // CRITICAL: this is the extra ID for address to send coins to swaps provider
6621
+ fromCurrencyExtraId: (_result$fromCurrencyE = result.fromCurrencyExtraId) != null ? _result$fromCurrencyE : "",
6622
+ // CRITICAL: this is the extra ID for address to send coins to swaps provider
6623
+ fixed: result.fixed
6420
6624
  };
6421
6625
  _this8._savePublicSwapIdLocally(result.swapId);
6422
6626
  Logger.log("Returning: " + safeStringify(_extends({}, toReturn, {
@@ -6677,9 +6881,11 @@ PublicSwapService.PUBLIC_SWAPS_COMMON_ERRORS = {
6677
6881
  PublicSwapService.PUBLIC_SWAP_DETAILS_FAIL_REASONS = {
6678
6882
  AMOUNT_LESS_THAN_MIN_SWAPPABLE: "amountLessThanMinSwappable",
6679
6883
  AMOUNT_HIGHER_THAN_MAX_SWAPPABLE: "amountHigherThanMaxSwappable",
6680
- PAIR_NOT_SUPPORTED: "pairNotSupported"
6884
+ PAIR_NOT_SUPPORTED: "pairNotSupported",
6885
+ NO_FIXED_BUT_HAVE_FLOATING_PUBLIC_SWAP_OPTION: "noFixedButHaveFloatingPublicSwapOption",
6886
+ NO_FLOATING_BUT_HAVE_FIXED_PUBLIC_SWAP_OPTION: "noFloatingButHaveFixedPublicSwapOption"
6681
6887
  };
6682
6888
  PublicSwapService._fiatDecimalsCount = FiatCurrenciesService.getCurrencyDecimalCountByCode("USD");
6683
6889
 
6684
- export { AmountUtils, ApiGroup, ApiGroups, AssetIcon, AxiosAdapter, BaseSwapCreationInfo, Blockchain, Button, Cache, CacheAndConcurrentRequestsResolver, CachedRobustExternalApiCallerService, CancelProcessing, Coin, ConcurrentCalculationsMetadataHolder, EmailsApi, ExistingSwap, ExistingSwapWithFiatData, ExternalApiProvider, FiatCurrenciesService, LoadingDots, Logger, LogsStorage, Protocol, PublicSwapService, RobustExternalAPICallerService, SupportChat, SwapProvider, SwapUtils, SwapspaceSwapProvider, getQueryParameterSingleValue, getQueryParameterValues, handleClickOutside, improveAndRethrow, logErrorOrOutputToConsole, postponeExecution, removeQueryParameterAndValues, safeStringify, saveQueryParameterAndValues, useCallHandlingErrors, useReferredState };
6890
+ export { AmountUtils, ApiGroup, ApiGroups, AssetIcon, AxiosAdapter, BaseSwapCreationInfo, Blockchain, Button, Cache, CacheAndConcurrentRequestsResolver, CachedRobustExternalApiCallerService, CancelProcessing, Coin, ConcurrentCalculationsMetadataHolder, EmailsApi, ExistingSwap, ExistingSwapWithFiatData, ExternalApiProvider, FiatCurrenciesService, InputValuesProviders, IpAddressProvider, LoadingDots, Logger, LogsStorage, Protocol, PublicSwapService, RobustExternalAPICallerService, SupportChat, SwapProvider, SwapUtils, SwapspaceSwapProvider, getQueryParameterSingleValue, getQueryParameterValues, handleClickOutside, improveAndRethrow, logErrorOrOutputToConsole, postponeExecution, removeQueryParameterAndValues, safeStringify, saveQueryParameterAndValues, useCallHandlingErrors, useReferredState };
6685
6891
  //# sourceMappingURL=index.module.js.map