@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.
- package/.gitlab-ci.yml +29 -0
- package/.husky/commit-msg +8 -0
- package/.husky/pre-push +1 -0
- package/README.md +13 -4
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/clover.xml +6516 -0
- package/coverage/coverage-final.json +43 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +416 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/rabbit-ui-kit/index.html +116 -0
- package/coverage/rabbit-ui-kit/index.js.html +88 -0
- package/coverage/rabbit-ui-kit/src/common/adapters/axiosAdapter.js.html +190 -0
- package/coverage/rabbit-ui-kit/src/common/adapters/index.html +116 -0
- package/coverage/rabbit-ui-kit/src/common/amountUtils.js.html +1393 -0
- package/coverage/rabbit-ui-kit/src/common/errorUtils.js.html +211 -0
- package/coverage/rabbit-ui-kit/src/common/external-apis/apiGroups.js.html +250 -0
- package/coverage/rabbit-ui-kit/src/common/external-apis/index.html +131 -0
- package/coverage/rabbit-ui-kit/src/common/external-apis/ipAddressProviders.js.html +499 -0
- package/coverage/rabbit-ui-kit/src/common/fiatCurrenciesService.js.html +568 -0
- package/coverage/rabbit-ui-kit/src/common/index.html +146 -0
- package/coverage/rabbit-ui-kit/src/common/models/blockchain.js.html +115 -0
- package/coverage/rabbit-ui-kit/src/common/models/coin.js.html +556 -0
- package/coverage/rabbit-ui-kit/src/common/models/index.html +146 -0
- package/coverage/rabbit-ui-kit/src/common/models/protocol.js.html +100 -0
- package/coverage/rabbit-ui-kit/src/common/utils/cache.js.html +889 -0
- package/coverage/rabbit-ui-kit/src/common/utils/emailAPI.js.html +139 -0
- package/coverage/rabbit-ui-kit/src/common/utils/index.html +161 -0
- package/coverage/rabbit-ui-kit/src/common/utils/logging/index.html +131 -0
- package/coverage/rabbit-ui-kit/src/common/utils/logging/logger.js.html +229 -0
- package/coverage/rabbit-ui-kit/src/common/utils/logging/logsStorage.js.html +268 -0
- package/coverage/rabbit-ui-kit/src/common/utils/postponeExecution.js.html +118 -0
- package/coverage/rabbit-ui-kit/src/common/utils/safeStringify.js.html +235 -0
- package/coverage/rabbit-ui-kit/src/components/atoms/AssetIcon/AssetIcon.jsx.html +250 -0
- package/coverage/rabbit-ui-kit/src/components/atoms/AssetIcon/index.html +116 -0
- package/coverage/rabbit-ui-kit/src/components/atoms/LoadingDots/LoadingDots.jsx.html +256 -0
- package/coverage/rabbit-ui-kit/src/components/atoms/LoadingDots/index.html +116 -0
- package/coverage/rabbit-ui-kit/src/components/atoms/SupportChat/SupportChat.jsx.html +229 -0
- package/coverage/rabbit-ui-kit/src/components/atoms/SupportChat/index.html +116 -0
- package/coverage/rabbit-ui-kit/src/components/atoms/buttons/Button/Button.jsx.html +802 -0
- package/coverage/rabbit-ui-kit/src/components/atoms/buttons/Button/index.html +116 -0
- package/coverage/rabbit-ui-kit/src/components/hooks/index.html +131 -0
- package/coverage/rabbit-ui-kit/src/components/hooks/useCallHandlingErrors.js.html +163 -0
- package/coverage/rabbit-ui-kit/src/components/hooks/useReferredState.js.html +157 -0
- package/coverage/rabbit-ui-kit/src/components/utils/index.html +146 -0
- package/coverage/rabbit-ui-kit/src/components/utils/inputValueProviders.js.html +259 -0
- package/coverage/rabbit-ui-kit/src/components/utils/uiUtils.js.html +127 -0
- package/coverage/rabbit-ui-kit/src/components/utils/urlQueryUtils.js.html +346 -0
- package/coverage/rabbit-ui-kit/src/index.html +116 -0
- package/coverage/rabbit-ui-kit/src/index.js.html +250 -0
- package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/cacheAndConcurrentRequestsResolver.js.html +1762 -0
- package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/cachedRobustExternalApiCallerService.js.html +649 -0
- package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/cancelProcessing.js.html +172 -0
- package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/concurrentCalculationsMetadataHolder.js.html +394 -0
- package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/externalApiProvider.js.html +553 -0
- package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/externalServicesStatsCollector.js.html +331 -0
- package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/index.html +206 -0
- package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/robustExternalAPICallerService.js.html +1249 -0
- package/coverage/rabbit-ui-kit/src/swaps-lib/external-apis/index.html +131 -0
- package/coverage/rabbit-ui-kit/src/swaps-lib/external-apis/swapProvider.js.html +727 -0
- package/coverage/rabbit-ui-kit/src/swaps-lib/external-apis/swapspaceSwapProvider.js.html +2899 -0
- package/coverage/rabbit-ui-kit/src/swaps-lib/models/baseSwapCreationInfo.js.html +214 -0
- package/coverage/rabbit-ui-kit/src/swaps-lib/models/existingSwap.js.html +304 -0
- package/coverage/rabbit-ui-kit/src/swaps-lib/models/existingSwapWithFiatData.js.html +487 -0
- package/coverage/rabbit-ui-kit/src/swaps-lib/models/index.html +146 -0
- package/coverage/rabbit-ui-kit/src/swaps-lib/services/index.html +116 -0
- package/coverage/rabbit-ui-kit/src/swaps-lib/services/publicSwapService.js.html +2191 -0
- package/coverage/rabbit-ui-kit/src/swaps-lib/utils/index.html +116 -0
- package/coverage/rabbit-ui-kit/src/swaps-lib/utils/swapUtils.js.html +742 -0
- package/coverage/rabbit-ui-kit/stories/atoms/LoadingDots.stories.jsx.html +226 -0
- package/coverage/rabbit-ui-kit/stories/atoms/buttons/Button.stories.jsx.html +946 -0
- package/coverage/rabbit-ui-kit/stories/atoms/buttons/index.html +116 -0
- package/coverage/rabbit-ui-kit/stories/atoms/index.html +116 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +196 -0
- package/dist/index.cjs +1706 -1498
- package/dist/index.cjs.map +1 -1
- package/dist/index.modern.js +817 -666
- package/dist/index.modern.js.map +1 -1
- package/dist/index.module.js +1704 -1498
- package/dist/index.module.js.map +1 -1
- package/dist/index.umd.js +1664 -1456
- package/dist/index.umd.js.map +1 -1
- package/package.json +11 -3
- package/src/common/amountUtils.js +4 -2
- package/src/common/external-apis/ipAddressProviders.js +138 -0
- package/src/common/tests/integration/external-apis/ipAddressProviders/getClientIpAddress.test.js +14 -0
- package/src/common/utils/cache.js +4 -4
- package/src/components/tests/utils/inputValueProviders/provideFormatOfFloatValueByInputString.test.js +139 -0
- package/src/components/tests/utils/urlQueryUtils/getQueryParameterValues.test.js +71 -0
- package/src/components/tests/utils/urlQueryUtils/saveQueryParameterAndValues.test.js +144 -0
- package/src/components/utils/inputValueProviders.js +58 -0
- package/src/index.js +2 -0
- package/src/robustExteranlApiCallerService/robustExternalAPICallerService.js +4 -2
- package/src/robustExteranlApiCallerService/tests/robustExternalAPICallerService/robustExternalAPICallerService/callExternalAPI/_performCallAttempt.test.js +787 -0
- package/src/robustExteranlApiCallerService/tests/robustExternalAPICallerService/robustExternalAPICallerService/callExternalAPI/callExternalAPI.test.js +745 -0
- package/src/robustExteranlApiCallerService/tests/robustExternalAPICallerService/robustExternalAPICallerService/constructor.test.js +31 -0
- package/src/swaps-lib/external-apis/swapProvider.js +17 -4
- package/src/swaps-lib/external-apis/swapspaceSwapProvider.js +91 -30
- package/src/swaps-lib/models/baseSwapCreationInfo.js +4 -1
- package/src/swaps-lib/models/existingSwap.js +3 -0
- package/src/swaps-lib/models/existingSwapWithFiatData.js +4 -0
- package/src/swaps-lib/services/publicSwapService.js +32 -4
- package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/_fetchSupportedCurrenciesIfNeeded.test.js +506 -0
- package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/createSwap.test.js +1311 -0
- package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/getAllSupportedCurrencies.test.js +76 -0
- package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/getDepositCurrencies.test.js +82 -0
- package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/getSwapInfo.test.js +1892 -0
- package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/getWithdrawalCurrencies.test.js +111 -0
- package/src/swaps-lib/test/utils/swapUtils/safeHandleRequestsLimitExceeding.test.js +88 -0
package/dist/index.modern.js
CHANGED
|
@@ -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: {} };
|
|
@@ -1612,6 +1612,52 @@ const handleClickOutside = (exceptionsRefs, callback) => {
|
|
|
1612
1612
|
return () => document.removeEventListener("click", handleClick);
|
|
1613
1613
|
};
|
|
1614
1614
|
|
|
1615
|
+
class InputValuesProviders {
|
|
1616
|
+
/**
|
|
1617
|
+
* Designed to be called onKeyUp event of html input field for float value
|
|
1618
|
+
* Removes all prohibited stuff from the given float string and remains only allowed.
|
|
1619
|
+
* Removes digits before and after the dot.
|
|
1620
|
+
*
|
|
1621
|
+
* @param inputString {string} string to be corrected
|
|
1622
|
+
* @param maxValue {string} max value for the correcting float value
|
|
1623
|
+
* @param digitsAfterDot {number} count of digits after the dot that this method should provide, min 0
|
|
1624
|
+
* @return {string} corrected float value string
|
|
1625
|
+
*/
|
|
1626
|
+
static provideFormatOfFloatValueByInputString(inputString, digitsAfterDot = 2, maxValue = null) {
|
|
1627
|
+
var _parts$2;
|
|
1628
|
+
let value = inputString;
|
|
1629
|
+
if (!value) {
|
|
1630
|
+
return "";
|
|
1631
|
+
}
|
|
1632
|
+
if (digitsAfterDot < 0) {
|
|
1633
|
+
throw new Error("Min suffix length is 0, got " + digitsAfterDot);
|
|
1634
|
+
}
|
|
1635
|
+
value = value.replace(/[,]/g, "."); // replaces commas with dots
|
|
1636
|
+
value = value.replace(/[^0-9.]/g, ""); // remove non digits/dots
|
|
1637
|
+
value = value.replace(/^\./g, "0."); // adds leading zero
|
|
1638
|
+
value = value.replace(/\.+/g, "."); // replaces series of dots with single dot
|
|
1639
|
+
|
|
1640
|
+
let parts = value.split(".");
|
|
1641
|
+
if (parts.length > 2) {
|
|
1642
|
+
// removes all after second dot and itself
|
|
1643
|
+
parts = [parts[0], parts[1]];
|
|
1644
|
+
}
|
|
1645
|
+
if (maxValue != null) {
|
|
1646
|
+
var _parts$;
|
|
1647
|
+
const maxDigitsCountBeforeTheDot = BigNumber(maxValue).toFixed(0).length;
|
|
1648
|
+
if (((_parts$ = parts[0]) == null ? void 0 : _parts$.length) > maxDigitsCountBeforeTheDot) {
|
|
1649
|
+
// removes redundant prefix digits
|
|
1650
|
+
parts[0] = parts[0].substring(parts[0].length - maxDigitsCountBeforeTheDot, parts[0].length);
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
if (((_parts$2 = parts[1]) == null ? void 0 : _parts$2.length) > digitsAfterDot) {
|
|
1654
|
+
// removes redundant suffix digits
|
|
1655
|
+
parts[1] = parts[1].substring(0, digitsAfterDot);
|
|
1656
|
+
}
|
|
1657
|
+
return parts.join(".");
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1615
1661
|
const PARAMETER_VALUES_SEPARATOR = "|*|"; // Sting that with high probability will not be in the user's data
|
|
1616
1662
|
|
|
1617
1663
|
/**
|
|
@@ -1919,11 +1965,13 @@ class AmountUtils {
|
|
|
1919
1965
|
}
|
|
1920
1966
|
|
|
1921
1967
|
/**
|
|
1968
|
+
* Returns integer part of number as a string.
|
|
1969
|
+
*
|
|
1922
1970
|
* @param amount {BigNumber|number|string|null|undefined} The number value to be trimmed.
|
|
1923
1971
|
* HEX strings also allowed "0x..." and JS hex numbers
|
|
1924
1972
|
* @return {string|null}
|
|
1925
1973
|
*/
|
|
1926
|
-
static
|
|
1974
|
+
static toIntegerString(amount) {
|
|
1927
1975
|
return this.trim(amount, 0);
|
|
1928
1976
|
}
|
|
1929
1977
|
|
|
@@ -2004,7 +2052,7 @@ class AmountUtils {
|
|
|
2004
2052
|
leftNumber = leftNumber.times(multiplier);
|
|
2005
2053
|
}
|
|
2006
2054
|
}
|
|
2007
|
-
const leftAmountString = AmountUtils.
|
|
2055
|
+
const leftAmountString = AmountUtils.toIntegerString(leftNumber);
|
|
2008
2056
|
const rightAmountString = right != null ? right.toFixed(rightCurrencyDigitsAfterDots, BigNumber.ROUND_FLOOR) : null;
|
|
2009
2057
|
return `${leftAmountString} ${leftTicker} ~ ${rightAmountString != null ? rightAmountString : "?"} ${rightTicker}`;
|
|
2010
2058
|
} catch (e) {
|
|
@@ -2225,10 +2273,10 @@ class Coin {
|
|
|
2225
2273
|
*/
|
|
2226
2274
|
class Cache {
|
|
2227
2275
|
/**
|
|
2228
|
-
* @param eventBus {EventBus} EventBus.js lib instance
|
|
2229
|
-
* @param [noSessionEvents=[]] {string[]} array of events that will be treated as "no session"
|
|
2276
|
+
* @param [eventBus=null] {EventBus} EventBus.js lib instance if you plan to use Cache with events handling
|
|
2277
|
+
* @param [noSessionEvents=[]] {string[]} array of events that will be treated as "no session", you should pass EventBus to make it work
|
|
2230
2278
|
*/
|
|
2231
|
-
constructor(eventBus, noSessionEvents = []) {
|
|
2279
|
+
constructor(eventBus = null, noSessionEvents = []) {
|
|
2232
2280
|
this._cache = new Map();
|
|
2233
2281
|
this._eventDependentDataKeys = [];
|
|
2234
2282
|
this._noSessionEvents = noSessionEvents;
|
|
@@ -2303,7 +2351,7 @@ class Cache {
|
|
|
2303
2351
|
const eventAndKeys = this._eventDependentDataKeys.find(item => item[0] === event);
|
|
2304
2352
|
if (eventAndKeys) {
|
|
2305
2353
|
eventAndKeys.push(key);
|
|
2306
|
-
} else {
|
|
2354
|
+
} else if (this._eventBus) {
|
|
2307
2355
|
this._eventDependentDataKeys.push([event, key]);
|
|
2308
2356
|
this._eventBus.addEventListener(event, () => {
|
|
2309
2357
|
try {
|
|
@@ -2501,456 +2549,220 @@ class EmailsApi {
|
|
|
2501
2549
|
}
|
|
2502
2550
|
EmailsApi.serverEndpointEntity = "emails";
|
|
2503
2551
|
|
|
2504
|
-
|
|
2505
|
-
* This util helps to avoid duplicated calls to a shared resource.
|
|
2506
|
-
* It tracks is there currently active calculation for the specific cache id and make all other requests
|
|
2507
|
-
* with the same cache id waiting for this active calculation to be finished. When the calculation ends
|
|
2508
|
-
* the resolver allows all the waiting requesters to get the data from cache and start their own calculations.
|
|
2509
|
-
*
|
|
2510
|
-
* This class should be instantiated inside some other service where you need to request some resource concurrently.
|
|
2511
|
-
* Rules:
|
|
2512
|
-
* 1. When you need to make a request inside your main service call 'getCachedOrWaitForCachedOrAcquireLock'
|
|
2513
|
-
* on the instance of this class and await for the result. If the flag allowing to start calculation is true
|
|
2514
|
-
* then you can request data inside your main service. Otherwise you should use the cached data as an another
|
|
2515
|
-
* requester just finished the most resent requesting and there is actual data in the cache that
|
|
2516
|
-
* is returned to you here.
|
|
2517
|
-
* 1.1 Also you can acquire a lock directly if you don't want to get cached data. Use the corresponding method 'acquireLock'.
|
|
2518
|
-
*
|
|
2519
|
-
* 2. If you start requesting (when you successfully acquired the lock) then after receiving the result of your
|
|
2520
|
-
* requesting you should call the 'saveCachedData' so the retrieved data will appear in the cache.
|
|
2521
|
-
*
|
|
2522
|
-
* 3. If you successfully acquired the lock then you should after calling the 'saveCachedData' call
|
|
2523
|
-
* the 'releaseLock' - this is mandatory to release the lock and allow other requesters to perform their requests.
|
|
2524
|
-
* WARNING: If for any reason you forget to call this method then this class instance will wait perpetually for
|
|
2525
|
-
* the lock releasing and all your attempts to request the data will constantly fail. So usually call it
|
|
2526
|
-
* inside the 'finally' block.
|
|
2527
|
-
*
|
|
2528
|
-
* TODO: [tests, critical++] add unit tests - massively used logic and can produce sophisticated concurrency bugs
|
|
2529
|
-
*/
|
|
2530
|
-
class CacheAndConcurrentRequestsResolver {
|
|
2552
|
+
class ExternalApiProvider {
|
|
2531
2553
|
/**
|
|
2532
|
-
*
|
|
2533
|
-
*
|
|
2534
|
-
*
|
|
2535
|
-
*
|
|
2536
|
-
*
|
|
2537
|
-
*
|
|
2538
|
-
*
|
|
2539
|
-
*
|
|
2540
|
-
*
|
|
2541
|
-
*
|
|
2554
|
+
* Creates an instance of external api provider.
|
|
2555
|
+
*
|
|
2556
|
+
* If you need sub-request then use 'subRequestIndex' to check current request index in functions below.
|
|
2557
|
+
* Also use array for 'httpMethod'.
|
|
2558
|
+
*
|
|
2559
|
+
* If the endpoint of dedicated provider has pagination then you should customize the behavior using
|
|
2560
|
+
* "changeQueryParametersForPageNumber", "checkWhetherResponseIsForLastPage".
|
|
2561
|
+
*
|
|
2562
|
+
* We perform RPS counting all over the App to avoid blocking our clients due to abuses of the providers.
|
|
2563
|
+
*
|
|
2564
|
+
* @param endpoint {string} URL to the provider's endpoint. Note: you can customize it using composeQueryString
|
|
2565
|
+
* @param [httpMethod] {string|string[]} one of "get", "post", "put", "patch", "delete" or an array of these values
|
|
2566
|
+
* for request having sub-requests
|
|
2567
|
+
* @param [timeout] {number} number of milliseconds to wait for the response
|
|
2568
|
+
* @param [apiGroup] {ApiGroup} singleton object containing parameters of API group. Helpful when you use the same
|
|
2569
|
+
* api for different providers to avoid hardcoding RPS inside each provider what can cause mistakes
|
|
2570
|
+
* @param [specificHeaders] {Object} contains specific keys (headers) and values (their content) if needed for this provider
|
|
2571
|
+
* @param [maxPageLength] {number} optional number of items per page if the request supports pagination
|
|
2542
2572
|
*/
|
|
2543
|
-
constructor(
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
this.
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
this.
|
|
2560
|
-
|
|
2573
|
+
constructor(endpoint, httpMethod, timeout, apiGroup, specificHeaders = {}, maxPageLength = Number.MAX_SAFE_INTEGER) {
|
|
2574
|
+
this.endpoint = endpoint;
|
|
2575
|
+
this.httpMethod = httpMethod != null ? httpMethod : "get";
|
|
2576
|
+
// TODO: [refactoring, critical] We have two timeouts for robust data retrieval - here and inside the service method call, need to remain the only
|
|
2577
|
+
this.timeout = timeout != null ? timeout : 10000;
|
|
2578
|
+
// TODO: [refactoring, critical] We need single place for all RPSes as we use them as hardcoded constants now inside different services
|
|
2579
|
+
this.apiGroup = apiGroup;
|
|
2580
|
+
this.maxPageLength = maxPageLength != null ? maxPageLength : Number.MAX_SAFE_INTEGER;
|
|
2581
|
+
this.niceFactor = 1;
|
|
2582
|
+
this.specificHeaders = specificHeaders != null ? specificHeaders : {};
|
|
2583
|
+
}
|
|
2584
|
+
getRps() {
|
|
2585
|
+
var _this$apiGroup$rps;
|
|
2586
|
+
return (_this$apiGroup$rps = this.apiGroup.rps) != null ? _this$apiGroup$rps : 2;
|
|
2587
|
+
}
|
|
2588
|
+
isRpsExceeded() {
|
|
2589
|
+
return this.apiGroup.isRpsExceeded();
|
|
2590
|
+
}
|
|
2591
|
+
actualizeLastCalledTimestamp() {
|
|
2592
|
+
this.apiGroup.actualizeLastCalledTimestamp();
|
|
2593
|
+
}
|
|
2594
|
+
getApiGroupId() {
|
|
2595
|
+
return this.apiGroup.id;
|
|
2561
2596
|
}
|
|
2562
2597
|
|
|
2563
2598
|
/**
|
|
2564
|
-
*
|
|
2565
|
-
*
|
|
2566
|
-
* - returns you flag that you can start requesting data from the shared resource
|
|
2567
|
-
* - or if there is already started calculation waits until it is finished (removed from this service)
|
|
2568
|
-
* and returns you the retrieved data
|
|
2569
|
-
* - or just returns you the cached data
|
|
2570
|
-
*
|
|
2571
|
-
* 'canStartDataRetrieval' equal true means that the lock was acquired, and you should manually call 'saveCachedData'
|
|
2572
|
-
* if needed and then 'releaseLock' to mark this calculation as finished so other
|
|
2573
|
-
* requesters can take their share of the resource.
|
|
2599
|
+
* Some endpoint can require several sub requests. Example is one request to get confirmed transactions
|
|
2600
|
+
* and another request for unconfirmed transactions. You should override this method to return true for such requests.
|
|
2574
2601
|
*
|
|
2575
|
-
* @
|
|
2576
|
-
* @return {Promise<({
|
|
2577
|
-
* canStartDataRetrieval: true,
|
|
2578
|
-
* cachedData: any,
|
|
2579
|
-
* lockId: string
|
|
2580
|
-
* }|{
|
|
2581
|
-
* canStartDataRetrieval: false,
|
|
2582
|
-
* cachedData: any
|
|
2583
|
-
* })>}
|
|
2602
|
+
* @return {boolean} true if this provider requires several requests to retrieve the data
|
|
2584
2603
|
*/
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
var _cached2;
|
|
2588
|
-
const startedAtTimestamp = Date.now();
|
|
2589
|
-
let cached = this._cache.get(cacheId);
|
|
2590
|
-
let cachedDataBackupIsPresentButExpired = null;
|
|
2591
|
-
if (cached != null && !this._removeExpiredCacheAutomatically) {
|
|
2592
|
-
const lastUpdateTimestamp = this._cache.getLastUpdateTimestamp(cacheId);
|
|
2593
|
-
if ((lastUpdateTimestamp != null ? lastUpdateTimestamp : 0) + this._cacheTtlMs < Date.now()) {
|
|
2594
|
-
/*
|
|
2595
|
-
* Here we are manually clearing 'cached' value retrieved from cache to force the data loading.
|
|
2596
|
-
* But we save its value first to the backup variable to be able to return this value if ongoing
|
|
2597
|
-
* requesting fails.
|
|
2598
|
-
*/
|
|
2599
|
-
cachedDataBackupIsPresentButExpired = cached;
|
|
2600
|
-
cached = null;
|
|
2601
|
-
}
|
|
2602
|
-
}
|
|
2603
|
-
let calculationId = null;
|
|
2604
|
-
let isRetrievedCacheExpired = true;
|
|
2605
|
-
let isWaitingForActiveCalculationSucceeded;
|
|
2606
|
-
let weStillHaveSomeTimeToProceedExecution = true;
|
|
2607
|
-
while (calculationId == null && cached == null && isRetrievedCacheExpired && weStillHaveSomeTimeToProceedExecution) {
|
|
2608
|
-
const result = await this._requestsManager.startCalculationOrWaitForActiveToFinish(cacheId);
|
|
2609
|
-
calculationId = typeof result === "string" ? result : null;
|
|
2610
|
-
isWaitingForActiveCalculationSucceeded = typeof result === "boolean" ? result : null;
|
|
2611
|
-
cached = this._cache.get(cacheId);
|
|
2612
|
-
isRetrievedCacheExpired = isWaitingForActiveCalculationSucceeded && cached == null;
|
|
2613
|
-
weStillHaveSomeTimeToProceedExecution = Date.now() - startedAtTimestamp < this._maxExecutionTimeMs;
|
|
2614
|
-
}
|
|
2615
|
-
if (calculationId) {
|
|
2616
|
-
var _cached;
|
|
2617
|
-
return {
|
|
2618
|
-
canStartDataRetrieval: true,
|
|
2619
|
-
cachedData: (_cached = cached) != null ? _cached : cachedDataBackupIsPresentButExpired,
|
|
2620
|
-
lockId: calculationId
|
|
2621
|
-
};
|
|
2622
|
-
}
|
|
2623
|
-
return {
|
|
2624
|
-
canStartDataRetrieval: false,
|
|
2625
|
-
cachedData: (_cached2 = cached) != null ? _cached2 : cachedDataBackupIsPresentButExpired
|
|
2626
|
-
};
|
|
2627
|
-
} catch (e) {
|
|
2628
|
-
improveAndRethrow(e, `${this._bio}.getCachedOrWaitForCachedOrAcquireLock`);
|
|
2629
|
-
}
|
|
2604
|
+
doesRequireSubRequests() {
|
|
2605
|
+
return false;
|
|
2630
2606
|
}
|
|
2631
2607
|
|
|
2632
2608
|
/**
|
|
2633
|
-
*
|
|
2634
|
-
* Doesn't wait for the active calculation, doesn't acquire lock, just retrieves the current cache as it is.
|
|
2609
|
+
* Some endpoint support pagination. Override this method if so and implement corresponding methods.
|
|
2635
2610
|
*
|
|
2636
|
-
* @
|
|
2637
|
-
* @return {any}
|
|
2611
|
+
* @return {boolean} true if this provider requires several requests to retrieve the data
|
|
2638
2612
|
*/
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
return this._cache.get(cacheId);
|
|
2642
|
-
} catch (e) {
|
|
2643
|
-
improveAndRethrow(e, "getCached");
|
|
2644
|
-
}
|
|
2645
|
-
}
|
|
2646
|
-
_getTtl() {
|
|
2647
|
-
return this._removeExpiredCacheAutomatically ? this._cacheTtlMs : null;
|
|
2613
|
+
doesSupportPagination() {
|
|
2614
|
+
return false;
|
|
2648
2615
|
}
|
|
2649
2616
|
|
|
2650
2617
|
/**
|
|
2651
|
-
*
|
|
2652
|
-
* So if this method returns result === true you can start the data retrieval.
|
|
2618
|
+
* Composes a query string to be added to the endpoint of this provider.
|
|
2653
2619
|
*
|
|
2654
|
-
* @param
|
|
2655
|
-
* @
|
|
2620
|
+
* @param params {any[]} params array passed to the RobustExternalAPICallerService
|
|
2621
|
+
* @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
|
|
2622
|
+
* @returns {string} query string to be concatenated with endpoint
|
|
2656
2623
|
*/
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
return await this._requestsManager.acquireLock(cacheId);
|
|
2660
|
-
} catch (e) {
|
|
2661
|
-
improveAndRethrow(e, "acquireLock");
|
|
2662
|
-
}
|
|
2624
|
+
composeQueryString(params, subRequestIndex = 0) {
|
|
2625
|
+
return "";
|
|
2663
2626
|
}
|
|
2664
2627
|
|
|
2665
2628
|
/**
|
|
2666
|
-
*
|
|
2667
|
-
*
|
|
2668
|
-
* If the current lock id is not equal to the passed one the passed data will be ignored.
|
|
2669
|
-
* Or you can do the synchronous data merging on your side and pass the
|
|
2670
|
-
* wasDataMergedSynchronouslyWithMostRecentCacheState=true so your data will be stored
|
|
2671
|
-
* despite on the lockId.
|
|
2672
|
-
* WARNING: you should do this only if you are sure you perform the synchronous update.
|
|
2629
|
+
* Composes a body to be added to the request
|
|
2673
2630
|
*
|
|
2674
|
-
* @param
|
|
2675
|
-
* @param
|
|
2676
|
-
* @
|
|
2677
|
-
* @param [sessionDependentData=true] {boolean}
|
|
2678
|
-
* @param [wasDataMergedSynchronouslyWithMostRecentCacheState=false]
|
|
2631
|
+
* @param params {any[]} params array passed to the RobustExternalAPICallerService
|
|
2632
|
+
* @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
|
|
2633
|
+
* @returns {string}
|
|
2679
2634
|
*/
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
if (wasDataMergedSynchronouslyWithMostRecentCacheState || this._requestsManager.isTheLockActiveOne(cacheId, lockId)) {
|
|
2683
|
-
/* We save passed data only if the <caller> has the currently acquired lockId.
|
|
2684
|
-
* If the passed lockId is not the active one it means that other code cleared/stopped the lock
|
|
2685
|
-
* acquired by the <caller> recently due to some urgent/more prior changes.
|
|
2686
|
-
*
|
|
2687
|
-
* But we allow user to pass the 'wasDataMergedSynchronouslyWithMostRecentCacheState' flag
|
|
2688
|
-
* that tells us that the user had taken the most recent cache value and merged his new data
|
|
2689
|
-
* with that cached value (AFTER possibly performing async data retrieval). This means that we
|
|
2690
|
-
* can ignore the fact that his lockId is no more relevant and save the passed data
|
|
2691
|
-
* as it is synchronously merged with the most recent cached data. (Synchronously merged means that
|
|
2692
|
-
* the lost update cannot occur during the merge time as JS execute the synchronous functions\
|
|
2693
|
-
* till the end).
|
|
2694
|
-
*/
|
|
2695
|
-
if (sessionDependentData) {
|
|
2696
|
-
this._cache.putSessionDependentData(cacheId, data, this._getTtl());
|
|
2697
|
-
} else {
|
|
2698
|
-
this._cache.put(cacheId, data, this._getTtl());
|
|
2699
|
-
}
|
|
2700
|
-
}
|
|
2701
|
-
} catch (e) {
|
|
2702
|
-
improveAndRethrow(e, `${this._bio}.saveCachedData`);
|
|
2703
|
-
}
|
|
2635
|
+
composeBody(params, subRequestIndex = 0) {
|
|
2636
|
+
return "";
|
|
2704
2637
|
}
|
|
2705
2638
|
|
|
2706
2639
|
/**
|
|
2707
|
-
*
|
|
2640
|
+
* Extracts data from the response and returns it
|
|
2708
2641
|
*
|
|
2709
|
-
* @param
|
|
2710
|
-
* @param
|
|
2642
|
+
* @param response {Object} HTTP response returned by provider
|
|
2643
|
+
* @param [params] {any[]} params array passed to the RobustExternalAPICallerService
|
|
2644
|
+
* @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
|
|
2645
|
+
* @param iterationsData {any[]} array of data retrieved from previous sub-requests
|
|
2646
|
+
* @returns {any}
|
|
2711
2647
|
*/
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
if (this._requestsManager.isTheLockActiveOne(cacheId, lockId)) {
|
|
2715
|
-
this._requestsManager.finishActiveCalculation(cacheId);
|
|
2716
|
-
}
|
|
2717
|
-
} catch (e) {
|
|
2718
|
-
improveAndRethrow(e, `${this._bio}.releaseLock`);
|
|
2719
|
-
}
|
|
2648
|
+
getDataByResponse(response, params = [], subRequestIndex = 0, iterationsData = []) {
|
|
2649
|
+
return [];
|
|
2720
2650
|
}
|
|
2721
2651
|
|
|
2722
2652
|
/**
|
|
2723
|
-
*
|
|
2653
|
+
* Function changing the query string according to page number and previous response
|
|
2654
|
+
* Only for endpoints supporting pagination
|
|
2724
2655
|
*
|
|
2725
|
-
* @param
|
|
2726
|
-
* @param
|
|
2727
|
-
*
|
|
2728
|
-
*
|
|
2729
|
-
*
|
|
2730
|
-
*
|
|
2731
|
-
* }
|
|
2732
|
-
* the flag signals whether data was changed during the processing or not
|
|
2733
|
-
* @param [sessionDependent=true] {boolean} whether to mark the cache entry as session-dependent
|
|
2656
|
+
* @param params {any[]} params array passed to the RobustExternalAPICallerService
|
|
2657
|
+
* @param previousResponse {Object} HTTP response returned by provider for previous call (previous page)
|
|
2658
|
+
* @param pageNumber {number} new page number. We count from 0. You need to manually increment with 1 if your
|
|
2659
|
+
* provider counts pages starting with 1
|
|
2660
|
+
* @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
|
|
2661
|
+
* @returns {any[]}
|
|
2734
2662
|
*/
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
const cached = this._cache.get(cacheId);
|
|
2738
|
-
const result = synchronousCurrentCacheProcessor(cached);
|
|
2739
|
-
if (result != null && result.isModified && (result == null ? void 0 : result.data) != null) {
|
|
2740
|
-
if (sessionDependent) {
|
|
2741
|
-
this._cache.putSessionDependentData(cacheId, result == null ? void 0 : result.data, this._getTtl());
|
|
2742
|
-
} else {
|
|
2743
|
-
this._cache.put(cacheId, result == null ? void 0 : result.data, this._getTtl());
|
|
2744
|
-
}
|
|
2745
|
-
|
|
2746
|
-
/* Here we call the lock releasing to ensure the currently active calculation will be ignored.
|
|
2747
|
-
* This is needed to ensure no 'lost update'.
|
|
2748
|
-
* Lost update can occur if we change data in this method and after that some calculation finishes
|
|
2749
|
-
* having the earlier data as its base to calculate its data set result. And the earlier data
|
|
2750
|
-
* has no changes applied inside this method, so we will lose them.
|
|
2751
|
-
*
|
|
2752
|
-
* This is not so good solution: ideally, we should acquire lock before performing any data updating.
|
|
2753
|
-
* But the goal of this method is to provide an instant ability to update the cached data.
|
|
2754
|
-
* And if we start acquiring the lock here the data update can be postponed significantly.
|
|
2755
|
-
* And this kills the desired nature of this method.
|
|
2756
|
-
* So we better lose some data retrieval (means abusing the resource a bit) than lose
|
|
2757
|
-
* the instant update expected after this method execution.
|
|
2758
|
-
*/
|
|
2759
|
-
this._requestsManager.finishActiveCalculation(cacheId);
|
|
2760
|
-
}
|
|
2761
|
-
} catch (e) {
|
|
2762
|
-
improveAndRethrow(e, `${this._bio}.actualizeCachedData`);
|
|
2763
|
-
}
|
|
2764
|
-
}
|
|
2765
|
-
invalidate(key) {
|
|
2766
|
-
this._cache.invalidate(key);
|
|
2767
|
-
this._requestsManager.finishActiveCalculation(key);
|
|
2768
|
-
}
|
|
2769
|
-
invalidateContaining(keyPart) {
|
|
2770
|
-
this._cache.invalidateContaining(keyPart);
|
|
2771
|
-
this._requestsManager.finishAllActiveCalculations(keyPart);
|
|
2772
|
-
}
|
|
2773
|
-
markAsExpiredButDontRemove(key) {
|
|
2774
|
-
if (this._removeExpiredCacheAutomatically) {
|
|
2775
|
-
this._cache.markCacheItemAsExpiredButDontRemove(key, this._cacheTtlMs);
|
|
2776
|
-
} else {
|
|
2777
|
-
this._cache.setLastUpdateTimestamp(key, Date.now() - this._cacheTtlMs - 1);
|
|
2778
|
-
}
|
|
2779
|
-
this._requestsManager.finishAllActiveCalculations(key);
|
|
2663
|
+
changeQueryParametersForPageNumber(params, previousResponse, pageNumber, subRequestIndex = 0) {
|
|
2664
|
+
return params;
|
|
2780
2665
|
}
|
|
2781
|
-
}
|
|
2782
2666
|
|
|
2783
|
-
/**
|
|
2784
|
-
* Util class to control access to a resource when it can be called in parallel for the same result.
|
|
2785
|
-
* (E.g. getting today coins-fiat rates from some API).
|
|
2786
|
-
*/
|
|
2787
|
-
class ManagerOfRequestsToTheSameResource {
|
|
2788
2667
|
/**
|
|
2789
|
-
*
|
|
2790
|
-
*
|
|
2791
|
-
*
|
|
2668
|
+
* Function checking whether the response is for the last page to stop requesting for a next page.
|
|
2669
|
+
* Only for endpoints supporting pagination.
|
|
2670
|
+
*
|
|
2671
|
+
* @param previousResponse {Object} HTTP response returned by provider for previous call (previous page)
|
|
2672
|
+
* @param currentResponse {Object} HTTP response returned by provider for current call (current page, next after the previous)
|
|
2673
|
+
* @param currentPageNumber {number} current page number (for current response)
|
|
2674
|
+
* @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
|
|
2675
|
+
* @returns {boolean}
|
|
2792
2676
|
*/
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
this.maxPollsCount = maxPollsCount;
|
|
2796
|
-
this.timeoutDuration = timeoutDuration;
|
|
2797
|
-
this._activeCalculationsIds = new Map();
|
|
2798
|
-
this._nextCalculationIds = new Map();
|
|
2677
|
+
checkWhetherResponseIsForLastPage(previousResponse, currentResponse, currentPageNumber, subRequestIndex = 0) {
|
|
2678
|
+
return true;
|
|
2799
2679
|
}
|
|
2800
2680
|
|
|
2801
2681
|
/**
|
|
2802
|
-
*
|
|
2803
|
-
* If there is active calculation waits until it removed from the active calculation uuid variable.
|
|
2804
|
-
*
|
|
2805
|
-
* @param requestHash {string}
|
|
2806
|
-
* @return {Promise<string|boolean>} returns uuid of new active calculation or true if waiting for active
|
|
2807
|
-
* calculation succeed or false if max attempts count exceeded
|
|
2682
|
+
* Resets the nice factor to default value
|
|
2808
2683
|
*/
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
const activeCalculationIdForHash = this._activeCalculationsIds.get(requestHash);
|
|
2812
|
-
if (activeCalculationIdForHash == null) {
|
|
2813
|
-
const id = v4();
|
|
2814
|
-
this._activeCalculationsIds.set(requestHash, id);
|
|
2815
|
-
return id;
|
|
2816
|
-
}
|
|
2817
|
-
return await this._waitForCalculationIdToFinish(requestHash, activeCalculationIdForHash, 0);
|
|
2818
|
-
} catch (e) {
|
|
2819
|
-
Logger.logError(e, `startCalculationOrWaitForActiveToFinish_${this.bio}`);
|
|
2820
|
-
}
|
|
2821
|
-
return null;
|
|
2684
|
+
resetNiceFactor() {
|
|
2685
|
+
this.niceFactor = 1;
|
|
2822
2686
|
}
|
|
2823
2687
|
|
|
2824
2688
|
/**
|
|
2825
|
-
*
|
|
2689
|
+
* Internal method used for requests requiring sub-requests.
|
|
2826
2690
|
*
|
|
2827
|
-
* @param
|
|
2828
|
-
* @return {
|
|
2829
|
-
* acquired, false if the max allowed time to wait for acquisition expired or any unexpected error occurs
|
|
2830
|
-
* during the waiting.
|
|
2691
|
+
* @param iterationsData {any[]} iterations data retrieved from getDataByResponse called per sub-request.
|
|
2692
|
+
* @return {any} by default flatten the passed iterations data array. Should be redefined if you need another logic.
|
|
2831
2693
|
*/
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
var _this$_nextCalculatio;
|
|
2835
|
-
const activeId = this._activeCalculationsIds.get(requestHash);
|
|
2836
|
-
const nextId = v4();
|
|
2837
|
-
if (activeId == null) {
|
|
2838
|
-
this._activeCalculationsIds.set(requestHash, nextId);
|
|
2839
|
-
return {
|
|
2840
|
-
result: true,
|
|
2841
|
-
lockId: nextId
|
|
2842
|
-
};
|
|
2843
|
-
}
|
|
2844
|
-
const currentNext = (_this$_nextCalculatio = this._nextCalculationIds.get(requestHash)) != null ? _this$_nextCalculatio : [];
|
|
2845
|
-
currentNext.push(nextId);
|
|
2846
|
-
this._nextCalculationIds.set(requestHash, currentNext);
|
|
2847
|
-
const waitingResult = await this._waitForCalculationIdToFinish(requestHash, activeId, 0, nextId);
|
|
2848
|
-
return {
|
|
2849
|
-
result: waitingResult,
|
|
2850
|
-
lockId: waitingResult ? nextId : undefined
|
|
2851
|
-
};
|
|
2852
|
-
} catch (e) {
|
|
2853
|
-
improveAndRethrow(e, "acquireLock");
|
|
2854
|
-
}
|
|
2694
|
+
incorporateIterationsData(iterationsData) {
|
|
2695
|
+
return iterationsData.flat();
|
|
2855
2696
|
}
|
|
2697
|
+
}
|
|
2856
2698
|
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
this._activeCalculationsIds.set(requestHash, next[0]);
|
|
2870
|
-
this._nextCalculationIds.set(requestHash, next.slice(1));
|
|
2871
|
-
}
|
|
2872
|
-
} catch (e) {
|
|
2873
|
-
improveAndRethrow(e, "finishActiveCalculation");
|
|
2874
|
-
}
|
|
2699
|
+
/**
|
|
2700
|
+
* Models a group of APIs provided by the same owner and used for different services in our app.
|
|
2701
|
+
* It means we need to mention RPS several times for each usage and also have some holder of last call timestamp per
|
|
2702
|
+
* api group. So this concept allows to use it for exact ExternalApiProvider and make sure that you use the same
|
|
2703
|
+
* RPS value and make decisions on base of the same timestamp of last call to the API group owner.
|
|
2704
|
+
*/
|
|
2705
|
+
class ApiGroup {
|
|
2706
|
+
constructor(id, rps, backendProxyIdGenerator = null) {
|
|
2707
|
+
this.id = id;
|
|
2708
|
+
this.rps = rps;
|
|
2709
|
+
this.lastCalledTimestamp = null;
|
|
2710
|
+
this.backendProxyIdGenerator = backendProxyIdGenerator;
|
|
2875
2711
|
}
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
if (typeof hash === "string" && new RegExp(keyPart).test(hash)) {
|
|
2880
|
-
this.finishActiveCalculation(hash);
|
|
2881
|
-
}
|
|
2882
|
-
});
|
|
2883
|
-
} catch (e) {
|
|
2884
|
-
improveAndRethrow(e, "finishAllActiveCalculations");
|
|
2885
|
-
}
|
|
2712
|
+
isRpsExceeded() {
|
|
2713
|
+
var _this$lastCalledTimes;
|
|
2714
|
+
return ((_this$lastCalledTimes = this.lastCalledTimestamp) != null ? _this$lastCalledTimes : 0) + Math.floor(1000 / this.rps) > Date.now();
|
|
2886
2715
|
}
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
* @param requestHash {string}
|
|
2890
|
-
* @param lockId {string}
|
|
2891
|
-
* @return {boolean}
|
|
2892
|
-
*/
|
|
2893
|
-
isTheLockActiveOne(requestHash, lockId) {
|
|
2894
|
-
try {
|
|
2895
|
-
return this._activeCalculationsIds.get(requestHash) === lockId;
|
|
2896
|
-
} catch (e) {
|
|
2897
|
-
improveAndRethrow(e, "isTheLockActiveOne");
|
|
2898
|
-
}
|
|
2716
|
+
actualizeLastCalledTimestamp() {
|
|
2717
|
+
this.lastCalledTimestamp = Date.now();
|
|
2899
2718
|
}
|
|
2900
|
-
|
|
2719
|
+
}
|
|
2720
|
+
const ApiGroups = {
|
|
2901
2721
|
/**
|
|
2902
|
-
*
|
|
2903
|
-
*
|
|
2904
|
-
* @param [attemptIndex=0] {number}
|
|
2905
|
-
* @param waitForCalculationId {string|null} if you want to wait for an exact id to appear as active then pass this parameter
|
|
2906
|
-
* @return {Promise<boolean>} true
|
|
2907
|
-
* - if the given calculation id is no more an active one
|
|
2908
|
-
* - or it is equal to waitForCalculationId
|
|
2909
|
-
* false
|
|
2910
|
-
* - if waiting period exceeds the max allowed waiting time or unexpected error occurs
|
|
2911
|
-
* @private
|
|
2722
|
+
* Currently we use free version of etherscan provider with 0.2 RPS. But we have API key with 100k requests free
|
|
2723
|
+
* per month. So we can add it if not enough current RPS.
|
|
2912
2724
|
*/
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
}
|
|
2725
|
+
ETHERSCAN: new ApiGroup("etherscan", 0.17),
|
|
2726
|
+
// Actually 0.2 but fails sometime, so we use smaller
|
|
2727
|
+
ALCHEMY: new ApiGroup("alchemy", 0.3, networkKey => `alchemy-${networkKey}`),
|
|
2728
|
+
BLOCKSTREAM: new ApiGroup("blockstream", 0.2),
|
|
2729
|
+
BLOCKCHAIN_INFO: new ApiGroup("blockchain.info", 1),
|
|
2730
|
+
BLOCKNATIVE: new ApiGroup("blocknative", 0.5),
|
|
2731
|
+
ETHGASSTATION: new ApiGroup("ethgasstation", 0.5),
|
|
2732
|
+
TRONGRID: new ApiGroup("trongrid", 0.3, networkKey => `trongrid-${networkKey}`),
|
|
2733
|
+
TRONSCAN: new ApiGroup("tronscan", 0.3),
|
|
2734
|
+
GETBLOCK: new ApiGroup("getblock", 0.3),
|
|
2735
|
+
COINCAP: new ApiGroup("coincap", 0.5),
|
|
2736
|
+
// 200 per minute without API key
|
|
2737
|
+
COINGECKO: new ApiGroup("coingecko", 0.9),
|
|
2738
|
+
// actually 0.13-0.5 according to the docs but we use smaller due to expirienced frequent abuses
|
|
2739
|
+
MESSARI: new ApiGroup("messari", 0.2),
|
|
2740
|
+
BTCCOM: new ApiGroup("btccom", 0.2),
|
|
2741
|
+
BITAPS: new ApiGroup("bitaps", 0.25),
|
|
2742
|
+
// Docs say that RPS is 3 but using it causes frequent 429 HTTP errors
|
|
2743
|
+
CEX: new ApiGroup("cex", 0.5),
|
|
2744
|
+
// Just assumption for RPS
|
|
2745
|
+
BIGDATACLOUD: new ApiGroup("bigdatacloud", 1),
|
|
2746
|
+
// Just assumption for RPS
|
|
2747
|
+
TRACKIP: new ApiGroup("trackip", 1),
|
|
2748
|
+
// Just assumption for RPS
|
|
2749
|
+
IPIFY: new ApiGroup("ipify", 1),
|
|
2750
|
+
// Just assumption for RPS
|
|
2751
|
+
WHATISMYIPADDRESS: new ApiGroup("whatismyipaddress", 1),
|
|
2752
|
+
// Just assumption for RPS
|
|
2753
|
+
EXCHANGERATE: new ApiGroup("exchangerate", 1),
|
|
2754
|
+
// Just assumption for RPS
|
|
2755
|
+
FRANKFURTER: new ApiGroup("frankfurter", 1),
|
|
2756
|
+
// Just assumption for RPS
|
|
2757
|
+
BITGO: new ApiGroup("bitgo", 1),
|
|
2758
|
+
// Just assumption for RPS
|
|
2759
|
+
BITCOINER: new ApiGroup("bitcoiner", 1),
|
|
2760
|
+
// Just assumption for RPS
|
|
2761
|
+
BITCORE: new ApiGroup("bitcore", 1),
|
|
2762
|
+
// Just assumption for RPS
|
|
2763
|
+
// BLOCKCHAIR: new ApiGroup("blockchair", 0.04), // this provider require API key for commercial use (10usd 10000 reqs), we will add it later
|
|
2764
|
+
MEMPOOL: new ApiGroup("mempool", 0.2) // Just assumption for RPS
|
|
2765
|
+
};
|
|
2954
2766
|
|
|
2955
2767
|
// TODO: [refactoring, low] Consider removing this logic task_id=c360f2af75764bde8badd9ff1cc00d48
|
|
2956
2768
|
class ConcurrentCalculationsMetadataHolder {
|
|
@@ -3089,7 +2901,7 @@ class ExternalServicesStatsCollector {
|
|
|
3089
2901
|
*/
|
|
3090
2902
|
class RobustExternalAPICallerService {
|
|
3091
2903
|
static getStats() {
|
|
3092
|
-
this.statsCollector.getStats();
|
|
2904
|
+
return this.statsCollector.getStats();
|
|
3093
2905
|
}
|
|
3094
2906
|
|
|
3095
2907
|
/**
|
|
@@ -3109,7 +2921,9 @@ class RobustExternalAPICallerService {
|
|
|
3109
2921
|
this.providers = providersData;
|
|
3110
2922
|
providersData.forEach(provider => provider.resetNiceFactor());
|
|
3111
2923
|
this.bio = bio;
|
|
3112
|
-
this._logger =
|
|
2924
|
+
this._logger = (...args) => {
|
|
2925
|
+
Logger.logError(...args);
|
|
2926
|
+
};
|
|
3113
2927
|
}
|
|
3114
2928
|
/**
|
|
3115
2929
|
* Performs data retrieval from external APIs. Tries providers till the data is retrieved.
|
|
@@ -3301,12 +3115,463 @@ class RobustExternalAPICallerService {
|
|
|
3301
3115
|
return providersCopy.sort((p1, p2) => p2.niceFactor - p1.niceFactor);
|
|
3302
3116
|
}
|
|
3303
3117
|
}
|
|
3304
|
-
RobustExternalAPICallerService.statsCollector = new ExternalServicesStatsCollector();
|
|
3305
|
-
RobustExternalAPICallerService.defaultRPSFactor = 1;
|
|
3306
|
-
RobustExternalAPICallerService.rpsMultiplier = 1.05;
|
|
3307
|
-
function punishProvider(provider) {
|
|
3308
|
-
provider.niceFactor = provider.niceFactor - 1;
|
|
3309
|
-
}
|
|
3118
|
+
RobustExternalAPICallerService.statsCollector = new ExternalServicesStatsCollector();
|
|
3119
|
+
RobustExternalAPICallerService.defaultRPSFactor = 1;
|
|
3120
|
+
RobustExternalAPICallerService.rpsMultiplier = 1.05;
|
|
3121
|
+
function punishProvider(provider) {
|
|
3122
|
+
provider.niceFactor = provider.niceFactor - 1;
|
|
3123
|
+
}
|
|
3124
|
+
|
|
3125
|
+
/**
|
|
3126
|
+
* This util helps to avoid duplicated calls to a shared resource.
|
|
3127
|
+
* It tracks is there currently active calculation for the specific cache id and make all other requests
|
|
3128
|
+
* with the same cache id waiting for this active calculation to be finished. When the calculation ends
|
|
3129
|
+
* the resolver allows all the waiting requesters to get the data from cache and start their own calculations.
|
|
3130
|
+
*
|
|
3131
|
+
* This class should be instantiated inside some other service where you need to request some resource concurrently.
|
|
3132
|
+
* Rules:
|
|
3133
|
+
* 1. When you need to make a request inside your main service call 'getCachedOrWaitForCachedOrAcquireLock'
|
|
3134
|
+
* on the instance of this class and await for the result. If the flag allowing to start calculation is true
|
|
3135
|
+
* then you can request data inside your main service. Otherwise you should use the cached data as an another
|
|
3136
|
+
* requester just finished the most resent requesting and there is actual data in the cache that
|
|
3137
|
+
* is returned to you here.
|
|
3138
|
+
* 1.1 Also you can acquire a lock directly if you don't want to get cached data. Use the corresponding method 'acquireLock'.
|
|
3139
|
+
*
|
|
3140
|
+
* 2. If you start requesting (when you successfully acquired the lock) then after receiving the result of your
|
|
3141
|
+
* requesting you should call the 'saveCachedData' so the retrieved data will appear in the cache.
|
|
3142
|
+
*
|
|
3143
|
+
* 3. If you successfully acquired the lock then you should after calling the 'saveCachedData' call
|
|
3144
|
+
* the 'releaseLock' - this is mandatory to release the lock and allow other requesters to perform their requests.
|
|
3145
|
+
* WARNING: If for any reason you forget to call this method then this class instance will wait perpetually for
|
|
3146
|
+
* the lock releasing and all your attempts to request the data will constantly fail. So usually call it
|
|
3147
|
+
* inside the 'finally' block.
|
|
3148
|
+
*
|
|
3149
|
+
* TODO: [tests, critical++] add unit tests - massively used logic and can produce sophisticated concurrency bugs
|
|
3150
|
+
*/
|
|
3151
|
+
class CacheAndConcurrentRequestsResolver {
|
|
3152
|
+
/**
|
|
3153
|
+
* @param bio {string} unique identifier for the exact service
|
|
3154
|
+
* @param cache {Cache} cache
|
|
3155
|
+
* @param cacheTtl {number|null} time to live for cache ms. 0 or null means the cache cannot expire
|
|
3156
|
+
* @param [maxCallAttemptsToWaitForAlreadyRunningRequest=100] {number} number of request allowed to do waiting for
|
|
3157
|
+
* result before we fail the original request. Use custom value only if you need to make the attempts count
|
|
3158
|
+
* and polling interval changes.
|
|
3159
|
+
* @param [timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished=1000] {number}
|
|
3160
|
+
* timeout ms for polling for a result. if you change maxCallAttemptsToWaitForAlreadyRunningRequest
|
|
3161
|
+
* then this parameter maybe also require the custom value.
|
|
3162
|
+
* @param [removeExpiredCacheAutomatically=true] {boolean}
|
|
3163
|
+
*/
|
|
3164
|
+
constructor(bio, cache, cacheTtl, removeExpiredCacheAutomatically = true, maxCallAttemptsToWaitForAlreadyRunningRequest = 100, timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished = 1000) {
|
|
3165
|
+
if (cacheTtl != null && cacheTtl < timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished * 2) {
|
|
3166
|
+
/*
|
|
3167
|
+
* During the lifetime of this service e.g. if the data is being retrieved slowly we can get
|
|
3168
|
+
* RACE CONDITION when we constantly retrieve data and during retrieval it is expired, so we are trying
|
|
3169
|
+
* to retrieve it again and again.
|
|
3170
|
+
* We have a protection mechanism that we will wait no more than
|
|
3171
|
+
* maxCallAttemptsToWaitForAlreadyRunningRequest * timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished
|
|
3172
|
+
* but this additional check is aimed to reduce potential loading time for some requests.
|
|
3173
|
+
*/
|
|
3174
|
+
throw new Error(`DEV: Wrong parameters passed to construct ${bio} - TTL ${cacheTtl} should be 2 times greater than ${timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished}`);
|
|
3175
|
+
}
|
|
3176
|
+
this._bio = bio;
|
|
3177
|
+
this._cache = cache;
|
|
3178
|
+
this._cacheTtlMs = cacheTtl != null ? cacheTtl : null;
|
|
3179
|
+
this._maxExecutionTimeMs = maxCallAttemptsToWaitForAlreadyRunningRequest * timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished;
|
|
3180
|
+
this._removeExpiredCacheAutomatically = removeExpiredCacheAutomatically;
|
|
3181
|
+
this._requestsManager = new ManagerOfRequestsToTheSameResource(bio, maxCallAttemptsToWaitForAlreadyRunningRequest, timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished);
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
/**
|
|
3185
|
+
* When using this service this is the major method you should call to get data by cache id.
|
|
3186
|
+
* This method checks is there cached data and ether
|
|
3187
|
+
* - returns you flag that you can start requesting data from the shared resource
|
|
3188
|
+
* - or if there is already started calculation waits until it is finished (removed from this service)
|
|
3189
|
+
* and returns you the retrieved data
|
|
3190
|
+
* - or just returns you the cached data
|
|
3191
|
+
*
|
|
3192
|
+
* 'canStartDataRetrieval' equal true means that the lock was acquired, and you should manually call 'saveCachedData'
|
|
3193
|
+
* if needed and then 'releaseLock' to mark this calculation as finished so other
|
|
3194
|
+
* requesters can take their share of the resource.
|
|
3195
|
+
*
|
|
3196
|
+
* @param cacheId {string}
|
|
3197
|
+
* @return {Promise<({
|
|
3198
|
+
* canStartDataRetrieval: true,
|
|
3199
|
+
* cachedData: any,
|
|
3200
|
+
* lockId: string
|
|
3201
|
+
* }|{
|
|
3202
|
+
* canStartDataRetrieval: false,
|
|
3203
|
+
* cachedData: any
|
|
3204
|
+
* })>}
|
|
3205
|
+
*/
|
|
3206
|
+
async getCachedOrWaitForCachedOrAcquireLock(cacheId) {
|
|
3207
|
+
try {
|
|
3208
|
+
var _cached2;
|
|
3209
|
+
const startedAtTimestamp = Date.now();
|
|
3210
|
+
let cached = this._cache.get(cacheId);
|
|
3211
|
+
let cachedDataBackupIsPresentButExpired = null;
|
|
3212
|
+
if (cached != null && !this._removeExpiredCacheAutomatically) {
|
|
3213
|
+
const lastUpdateTimestamp = this._cache.getLastUpdateTimestamp(cacheId);
|
|
3214
|
+
if ((lastUpdateTimestamp != null ? lastUpdateTimestamp : 0) + this._cacheTtlMs < Date.now()) {
|
|
3215
|
+
/*
|
|
3216
|
+
* Here we are manually clearing 'cached' value retrieved from cache to force the data loading.
|
|
3217
|
+
* But we save its value first to the backup variable to be able to return this value if ongoing
|
|
3218
|
+
* requesting fails.
|
|
3219
|
+
*/
|
|
3220
|
+
cachedDataBackupIsPresentButExpired = cached;
|
|
3221
|
+
cached = null;
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
let calculationId = null;
|
|
3225
|
+
let isRetrievedCacheExpired = true;
|
|
3226
|
+
let isWaitingForActiveCalculationSucceeded;
|
|
3227
|
+
let weStillHaveSomeTimeToProceedExecution = true;
|
|
3228
|
+
while (calculationId == null && cached == null && isRetrievedCacheExpired && weStillHaveSomeTimeToProceedExecution) {
|
|
3229
|
+
const result = await this._requestsManager.startCalculationOrWaitForActiveToFinish(cacheId);
|
|
3230
|
+
calculationId = typeof result === "string" ? result : null;
|
|
3231
|
+
isWaitingForActiveCalculationSucceeded = typeof result === "boolean" ? result : null;
|
|
3232
|
+
cached = this._cache.get(cacheId);
|
|
3233
|
+
isRetrievedCacheExpired = isWaitingForActiveCalculationSucceeded && cached == null;
|
|
3234
|
+
weStillHaveSomeTimeToProceedExecution = Date.now() - startedAtTimestamp < this._maxExecutionTimeMs;
|
|
3235
|
+
}
|
|
3236
|
+
if (calculationId) {
|
|
3237
|
+
var _cached;
|
|
3238
|
+
return {
|
|
3239
|
+
canStartDataRetrieval: true,
|
|
3240
|
+
cachedData: (_cached = cached) != null ? _cached : cachedDataBackupIsPresentButExpired,
|
|
3241
|
+
lockId: calculationId
|
|
3242
|
+
};
|
|
3243
|
+
}
|
|
3244
|
+
return {
|
|
3245
|
+
canStartDataRetrieval: false,
|
|
3246
|
+
cachedData: (_cached2 = cached) != null ? _cached2 : cachedDataBackupIsPresentButExpired
|
|
3247
|
+
};
|
|
3248
|
+
} catch (e) {
|
|
3249
|
+
improveAndRethrow(e, `${this._bio}.getCachedOrWaitForCachedOrAcquireLock`);
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
|
|
3253
|
+
/**
|
|
3254
|
+
* Returns just the current cache value for the given id.
|
|
3255
|
+
* Doesn't wait for the active calculation, doesn't acquire lock, just retrieves the current cache as it is.
|
|
3256
|
+
*
|
|
3257
|
+
* @param cacheId {string}
|
|
3258
|
+
* @return {any}
|
|
3259
|
+
*/
|
|
3260
|
+
getCached(cacheId) {
|
|
3261
|
+
try {
|
|
3262
|
+
return this._cache.get(cacheId);
|
|
3263
|
+
} catch (e) {
|
|
3264
|
+
improveAndRethrow(e, "getCached");
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
_getTtl() {
|
|
3268
|
+
return this._removeExpiredCacheAutomatically ? this._cacheTtlMs : null;
|
|
3269
|
+
}
|
|
3270
|
+
|
|
3271
|
+
/**
|
|
3272
|
+
* Directly acquires the lock despite on cached data availability.
|
|
3273
|
+
* So if this method returns result === true you can start the data retrieval.
|
|
3274
|
+
*
|
|
3275
|
+
* @param cacheId {string}
|
|
3276
|
+
* @return {Promise<{ result: true, lockId: string }|{ result: false }>}
|
|
3277
|
+
*/
|
|
3278
|
+
async acquireLock(cacheId) {
|
|
3279
|
+
try {
|
|
3280
|
+
return await this._requestsManager.acquireLock(cacheId);
|
|
3281
|
+
} catch (e) {
|
|
3282
|
+
improveAndRethrow(e, "acquireLock");
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
|
|
3286
|
+
/**
|
|
3287
|
+
* This method should be called only if you acquired a lock successfully.
|
|
3288
|
+
*
|
|
3289
|
+
* If the current lock id is not equal to the passed one the passed data will be ignored.
|
|
3290
|
+
* Or you can do the synchronous data merging on your side and pass the
|
|
3291
|
+
* wasDataMergedSynchronouslyWithMostRecentCacheState=true so your data will be stored
|
|
3292
|
+
* despite on the lockId.
|
|
3293
|
+
* WARNING: you should do this only if you are sure you perform the synchronous update.
|
|
3294
|
+
*
|
|
3295
|
+
* @param cacheId {string}
|
|
3296
|
+
* @param lockId {string}
|
|
3297
|
+
* @param data {any}
|
|
3298
|
+
* @param [sessionDependentData=true] {boolean}
|
|
3299
|
+
* @param [wasDataMergedSynchronouslyWithMostRecentCacheState=false]
|
|
3300
|
+
*/
|
|
3301
|
+
saveCachedData(cacheId, lockId, data, sessionDependentData = true, wasDataMergedSynchronouslyWithMostRecentCacheState = false) {
|
|
3302
|
+
try {
|
|
3303
|
+
if (wasDataMergedSynchronouslyWithMostRecentCacheState || this._requestsManager.isTheLockActiveOne(cacheId, lockId)) {
|
|
3304
|
+
/* We save passed data only if the <caller> has the currently acquired lockId.
|
|
3305
|
+
* If the passed lockId is not the active one it means that other code cleared/stopped the lock
|
|
3306
|
+
* acquired by the <caller> recently due to some urgent/more prior changes.
|
|
3307
|
+
*
|
|
3308
|
+
* But we allow user to pass the 'wasDataMergedSynchronouslyWithMostRecentCacheState' flag
|
|
3309
|
+
* that tells us that the user had taken the most recent cache value and merged his new data
|
|
3310
|
+
* with that cached value (AFTER possibly performing async data retrieval). This means that we
|
|
3311
|
+
* can ignore the fact that his lockId is no more relevant and save the passed data
|
|
3312
|
+
* as it is synchronously merged with the most recent cached data. (Synchronously merged means that
|
|
3313
|
+
* the lost update cannot occur during the merge time as JS execute the synchronous functions\
|
|
3314
|
+
* till the end).
|
|
3315
|
+
*/
|
|
3316
|
+
if (sessionDependentData) {
|
|
3317
|
+
this._cache.putSessionDependentData(cacheId, data, this._getTtl());
|
|
3318
|
+
} else {
|
|
3319
|
+
this._cache.put(cacheId, data, this._getTtl());
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
} catch (e) {
|
|
3323
|
+
improveAndRethrow(e, `${this._bio}.saveCachedData`);
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
|
|
3327
|
+
/**
|
|
3328
|
+
* Should be called then and only then if you successfully acquired a lock with the lock id.
|
|
3329
|
+
*
|
|
3330
|
+
* @param cacheId {string}
|
|
3331
|
+
* @param lockId {string}
|
|
3332
|
+
*/
|
|
3333
|
+
releaseLock(cacheId, lockId) {
|
|
3334
|
+
try {
|
|
3335
|
+
if (this._requestsManager.isTheLockActiveOne(cacheId, lockId)) {
|
|
3336
|
+
this._requestsManager.finishActiveCalculation(cacheId);
|
|
3337
|
+
}
|
|
3338
|
+
} catch (e) {
|
|
3339
|
+
improveAndRethrow(e, `${this._bio}.releaseLock`);
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
/**
|
|
3344
|
+
* Actualized currently present cached data by key. Applies the provided function to the cached data.
|
|
3345
|
+
*
|
|
3346
|
+
* @param cacheId {string} id of cache entry
|
|
3347
|
+
* @param synchronousCurrentCacheProcessor (function|null} synchronous function accepting cache entry. Should return
|
|
3348
|
+
* an object in following format:
|
|
3349
|
+
* {
|
|
3350
|
+
* isModified: boolean,
|
|
3351
|
+
* data: any
|
|
3352
|
+
* }
|
|
3353
|
+
* the flag signals whether data was changed during the processing or not
|
|
3354
|
+
* @param [sessionDependent=true] {boolean} whether to mark the cache entry as session-dependent
|
|
3355
|
+
*/
|
|
3356
|
+
actualizeCachedData(cacheId, synchronousCurrentCacheProcessor, sessionDependent = true) {
|
|
3357
|
+
try {
|
|
3358
|
+
const cached = this._cache.get(cacheId);
|
|
3359
|
+
const result = synchronousCurrentCacheProcessor(cached);
|
|
3360
|
+
if (result != null && result.isModified && (result == null ? void 0 : result.data) != null) {
|
|
3361
|
+
if (sessionDependent) {
|
|
3362
|
+
this._cache.putSessionDependentData(cacheId, result == null ? void 0 : result.data, this._getTtl());
|
|
3363
|
+
} else {
|
|
3364
|
+
this._cache.put(cacheId, result == null ? void 0 : result.data, this._getTtl());
|
|
3365
|
+
}
|
|
3366
|
+
|
|
3367
|
+
/* Here we call the lock releasing to ensure the currently active calculation will be ignored.
|
|
3368
|
+
* This is needed to ensure no 'lost update'.
|
|
3369
|
+
* Lost update can occur if we change data in this method and after that some calculation finishes
|
|
3370
|
+
* having the earlier data as its base to calculate its data set result. And the earlier data
|
|
3371
|
+
* has no changes applied inside this method, so we will lose them.
|
|
3372
|
+
*
|
|
3373
|
+
* This is not so good solution: ideally, we should acquire lock before performing any data updating.
|
|
3374
|
+
* But the goal of this method is to provide an instant ability to update the cached data.
|
|
3375
|
+
* And if we start acquiring the lock here the data update can be postponed significantly.
|
|
3376
|
+
* And this kills the desired nature of this method.
|
|
3377
|
+
* So we better lose some data retrieval (means abusing the resource a bit) than lose
|
|
3378
|
+
* the instant update expected after this method execution.
|
|
3379
|
+
*/
|
|
3380
|
+
this._requestsManager.finishActiveCalculation(cacheId);
|
|
3381
|
+
}
|
|
3382
|
+
} catch (e) {
|
|
3383
|
+
improveAndRethrow(e, `${this._bio}.actualizeCachedData`);
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
invalidate(key) {
|
|
3387
|
+
this._cache.invalidate(key);
|
|
3388
|
+
this._requestsManager.finishActiveCalculation(key);
|
|
3389
|
+
}
|
|
3390
|
+
invalidateContaining(keyPart) {
|
|
3391
|
+
this._cache.invalidateContaining(keyPart);
|
|
3392
|
+
this._requestsManager.finishAllActiveCalculations(keyPart);
|
|
3393
|
+
}
|
|
3394
|
+
markAsExpiredButDontRemove(key) {
|
|
3395
|
+
if (this._removeExpiredCacheAutomatically) {
|
|
3396
|
+
this._cache.markCacheItemAsExpiredButDontRemove(key, this._cacheTtlMs);
|
|
3397
|
+
} else {
|
|
3398
|
+
this._cache.setLastUpdateTimestamp(key, Date.now() - this._cacheTtlMs - 1);
|
|
3399
|
+
}
|
|
3400
|
+
this._requestsManager.finishAllActiveCalculations(key);
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3404
|
+
/**
|
|
3405
|
+
* Util class to control access to a resource when it can be called in parallel for the same result.
|
|
3406
|
+
* (E.g. getting today coins-fiat rates from some API).
|
|
3407
|
+
*/
|
|
3408
|
+
class ManagerOfRequestsToTheSameResource {
|
|
3409
|
+
/**
|
|
3410
|
+
* @param bio {string} resource-related identifier for logging
|
|
3411
|
+
* @param [maxPollsCount=100] {number} max number of attempts to wait when waiting for a lock acquisition
|
|
3412
|
+
* @param [timeoutDuration=1000] {number} timeout between the polls for a lock acquisition
|
|
3413
|
+
*/
|
|
3414
|
+
constructor(bio, maxPollsCount = 100, timeoutDuration = 1000) {
|
|
3415
|
+
this.bio = bio;
|
|
3416
|
+
this.maxPollsCount = maxPollsCount;
|
|
3417
|
+
this.timeoutDuration = timeoutDuration;
|
|
3418
|
+
this._activeCalculationsIds = new Map();
|
|
3419
|
+
this._nextCalculationIds = new Map();
|
|
3420
|
+
}
|
|
3421
|
+
|
|
3422
|
+
/**
|
|
3423
|
+
* If there is no active calculation just creates uuid and returns it.
|
|
3424
|
+
* If there is active calculation waits until it removed from the active calculation uuid variable.
|
|
3425
|
+
*
|
|
3426
|
+
* @param requestHash {string}
|
|
3427
|
+
* @return {Promise<string|boolean>} returns uuid of new active calculation or true if waiting for active
|
|
3428
|
+
* calculation succeed or false if max attempts count exceeded
|
|
3429
|
+
*/
|
|
3430
|
+
async startCalculationOrWaitForActiveToFinish(requestHash) {
|
|
3431
|
+
try {
|
|
3432
|
+
const activeCalculationIdForHash = this._activeCalculationsIds.get(requestHash);
|
|
3433
|
+
if (activeCalculationIdForHash == null) {
|
|
3434
|
+
const id = v4();
|
|
3435
|
+
this._activeCalculationsIds.set(requestHash, id);
|
|
3436
|
+
return id;
|
|
3437
|
+
}
|
|
3438
|
+
return await this._waitForCalculationIdToFinish(requestHash, activeCalculationIdForHash, 0);
|
|
3439
|
+
} catch (e) {
|
|
3440
|
+
Logger.logError(e, `startCalculationOrWaitForActiveToFinish_${this.bio}`);
|
|
3441
|
+
}
|
|
3442
|
+
return null;
|
|
3443
|
+
}
|
|
3444
|
+
|
|
3445
|
+
/**
|
|
3446
|
+
* Acquires lock to the resource by the provided hash.
|
|
3447
|
+
*
|
|
3448
|
+
* @param requestHash {string}
|
|
3449
|
+
* @return {Promise<{ result: true, lockId: string }|{ result: false }>} result is true if the lock is successfully
|
|
3450
|
+
* acquired, false if the max allowed time to wait for acquisition expired or any unexpected error occurs
|
|
3451
|
+
* during the waiting.
|
|
3452
|
+
*/
|
|
3453
|
+
async acquireLock(requestHash) {
|
|
3454
|
+
try {
|
|
3455
|
+
var _this$_nextCalculatio;
|
|
3456
|
+
const activeId = this._activeCalculationsIds.get(requestHash);
|
|
3457
|
+
const nextId = v4();
|
|
3458
|
+
if (activeId == null) {
|
|
3459
|
+
this._activeCalculationsIds.set(requestHash, nextId);
|
|
3460
|
+
return {
|
|
3461
|
+
result: true,
|
|
3462
|
+
lockId: nextId
|
|
3463
|
+
};
|
|
3464
|
+
}
|
|
3465
|
+
const currentNext = (_this$_nextCalculatio = this._nextCalculationIds.get(requestHash)) != null ? _this$_nextCalculatio : [];
|
|
3466
|
+
currentNext.push(nextId);
|
|
3467
|
+
this._nextCalculationIds.set(requestHash, currentNext);
|
|
3468
|
+
const waitingResult = await this._waitForCalculationIdToFinish(requestHash, activeId, 0, nextId);
|
|
3469
|
+
return {
|
|
3470
|
+
result: waitingResult,
|
|
3471
|
+
lockId: waitingResult ? nextId : undefined
|
|
3472
|
+
};
|
|
3473
|
+
} catch (e) {
|
|
3474
|
+
improveAndRethrow(e, "acquireLock");
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
|
|
3478
|
+
/**
|
|
3479
|
+
* Clears active calculation id.
|
|
3480
|
+
* WARNING: if you forget to call this method the start* one will perform maxPollsCount attempts before finishing
|
|
3481
|
+
* @param requestHash {string} hash of request. Helps to distinct the request for the same resource but
|
|
3482
|
+
* having different request parameters and hold a dedicated calculation id per this hash
|
|
3483
|
+
*/
|
|
3484
|
+
finishActiveCalculation(requestHash = "default") {
|
|
3485
|
+
try {
|
|
3486
|
+
var _this$_nextCalculatio2;
|
|
3487
|
+
this._activeCalculationsIds.delete(requestHash);
|
|
3488
|
+
const next = (_this$_nextCalculatio2 = this._nextCalculationIds.get(requestHash)) != null ? _this$_nextCalculatio2 : [];
|
|
3489
|
+
if (next.length) {
|
|
3490
|
+
this._activeCalculationsIds.set(requestHash, next[0]);
|
|
3491
|
+
this._nextCalculationIds.set(requestHash, next.slice(1));
|
|
3492
|
+
}
|
|
3493
|
+
} catch (e) {
|
|
3494
|
+
improveAndRethrow(e, "finishActiveCalculation");
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3497
|
+
finishAllActiveCalculations(keyPart = "") {
|
|
3498
|
+
try {
|
|
3499
|
+
Array.from(this._activeCalculationsIds.keys()).forEach(hash => {
|
|
3500
|
+
if (typeof hash === "string" && new RegExp(keyPart).test(hash)) {
|
|
3501
|
+
this.finishActiveCalculation(hash);
|
|
3502
|
+
}
|
|
3503
|
+
});
|
|
3504
|
+
} catch (e) {
|
|
3505
|
+
improveAndRethrow(e, "finishAllActiveCalculations");
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
|
|
3509
|
+
/**
|
|
3510
|
+
* @param requestHash {string}
|
|
3511
|
+
* @param lockId {string}
|
|
3512
|
+
* @return {boolean}
|
|
3513
|
+
*/
|
|
3514
|
+
isTheLockActiveOne(requestHash, lockId) {
|
|
3515
|
+
try {
|
|
3516
|
+
return this._activeCalculationsIds.get(requestHash) === lockId;
|
|
3517
|
+
} catch (e) {
|
|
3518
|
+
improveAndRethrow(e, "isTheLockActiveOne");
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3521
|
+
|
|
3522
|
+
/**
|
|
3523
|
+
* @param requestHash {string}
|
|
3524
|
+
* @param activeCalculationId {string|null}
|
|
3525
|
+
* @param [attemptIndex=0] {number}
|
|
3526
|
+
* @param waitForCalculationId {string|null} if you want to wait for an exact id to appear as active then pass this parameter
|
|
3527
|
+
* @return {Promise<boolean>} true
|
|
3528
|
+
* - if the given calculation id is no more an active one
|
|
3529
|
+
* - or it is equal to waitForCalculationId
|
|
3530
|
+
* false
|
|
3531
|
+
* - if waiting period exceeds the max allowed waiting time or unexpected error occurs
|
|
3532
|
+
* @private
|
|
3533
|
+
*/
|
|
3534
|
+
async _waitForCalculationIdToFinish(requestHash, activeCalculationId, attemptIndex = 0, waitForCalculationId = null) {
|
|
3535
|
+
try {
|
|
3536
|
+
if (attemptIndex + 1 > this.maxPollsCount) {
|
|
3537
|
+
// Max number of polls for active calculation id change is achieved. So we return false.
|
|
3538
|
+
return false;
|
|
3539
|
+
}
|
|
3540
|
+
const currentId = this._activeCalculationsIds.get(requestHash);
|
|
3541
|
+
if (waitForCalculationId == null ? currentId !== activeCalculationId : currentId === waitForCalculationId) {
|
|
3542
|
+
/* We return true depending on the usage of this function:
|
|
3543
|
+
* 1. if there is calculation id that we should wait for to become an active then we return true only
|
|
3544
|
+
* if this id becomes the active one.
|
|
3545
|
+
*
|
|
3546
|
+
* Theoretically we can fail to wait for the desired calculation id. This can be caused by wrong use of
|
|
3547
|
+
* this service or by any other mistakes/errors. But this waiting function will return false anyway if
|
|
3548
|
+
* the number of polls done exceeds the max allowed.
|
|
3549
|
+
*
|
|
3550
|
+
* 2. if we just wait for the currently active calculation id to be finished then we return true
|
|
3551
|
+
* when we notice that the current active id differs from the original passed into this function.
|
|
3552
|
+
*/
|
|
3553
|
+
return true;
|
|
3554
|
+
} else {
|
|
3555
|
+
/* The original calculation id is still the active one, so we are scheduling a new attempt to check
|
|
3556
|
+
* whether the active calculation id changed or not in timeoutDuration milliseconds.
|
|
3557
|
+
*/
|
|
3558
|
+
const it = this;
|
|
3559
|
+
return new Promise((resolve, reject) => {
|
|
3560
|
+
setTimeout(function () {
|
|
3561
|
+
try {
|
|
3562
|
+
resolve(it._waitForCalculationIdToFinish(requestHash, activeCalculationId, attemptIndex + 1));
|
|
3563
|
+
} catch (e) {
|
|
3564
|
+
reject(e);
|
|
3565
|
+
}
|
|
3566
|
+
}, this.timeoutDuration);
|
|
3567
|
+
});
|
|
3568
|
+
}
|
|
3569
|
+
} catch (e) {
|
|
3570
|
+
Logger.logError(e, "_waitForCalculationIdToFinish", "Failed to wait for active calculation id change.");
|
|
3571
|
+
return false;
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3310
3575
|
|
|
3311
3576
|
/**
|
|
3312
3577
|
* Extended edit of RobustExternalApiCallerService supporting cache and management of concurrent requests
|
|
@@ -3402,244 +3667,93 @@ class CachedRobustExternalApiCallerService {
|
|
|
3402
3667
|
}
|
|
3403
3668
|
}
|
|
3404
3669
|
|
|
3405
|
-
|
|
3406
|
-
* Utils class needed to perform cancelling of axios request inside some process.
|
|
3407
|
-
* Provides cancel state and axios token for HTTP requests
|
|
3408
|
-
*/
|
|
3409
|
-
class CancelProcessing {
|
|
3670
|
+
class BigdatacloudIpAddressProvider extends ExternalApiProvider {
|
|
3410
3671
|
constructor() {
|
|
3411
|
-
|
|
3412
|
-
this._isCanceled = false;
|
|
3413
|
-
}
|
|
3414
|
-
cancel() {
|
|
3415
|
-
this._isCanceled = true;
|
|
3416
|
-
this._cancelToken.cancel();
|
|
3417
|
-
}
|
|
3418
|
-
isCanceled() {
|
|
3419
|
-
return this._isCanceled;
|
|
3672
|
+
super("https://api.bigdatacloud.net/data/client-ip", "get", 15000, ApiGroups.BIGDATACLOUD);
|
|
3420
3673
|
}
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
static instance() {
|
|
3425
|
-
return new CancelProcessing();
|
|
3674
|
+
getDataByResponse(response, params = [], subRequestIndex = 0, iterationsData = []) {
|
|
3675
|
+
var _response$data;
|
|
3676
|
+
return (response == null ? void 0 : response.data) && ((_response$data = response.data) == null ? void 0 : _response$data.ipString);
|
|
3426
3677
|
}
|
|
3427
3678
|
}
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
* Creates an instance of external api provider.
|
|
3432
|
-
*
|
|
3433
|
-
* If you need sub-request then use 'subRequestIndex' to check current request index in functions below.
|
|
3434
|
-
* Also use array for 'httpMethod'.
|
|
3435
|
-
*
|
|
3436
|
-
* If the endpoint of dedicated provider has pagination then you should customize the behavior using
|
|
3437
|
-
* "changeQueryParametersForPageNumber", "checkWhetherResponseIsForLastPage".
|
|
3438
|
-
*
|
|
3439
|
-
* We perform RPS counting all over the App to avoid blocking our clients due to abuses of the providers.
|
|
3440
|
-
*
|
|
3441
|
-
* @param endpoint {string} URL to the provider's endpoint. Note: you can customize it using composeQueryString
|
|
3442
|
-
* @param [httpMethod] {string|string[]} one of "get", "post", "put", "patch", "delete" or an array of these values
|
|
3443
|
-
* for request having sub-requests
|
|
3444
|
-
* @param [timeout] {number} number of milliseconds to wait for the response
|
|
3445
|
-
* @param [apiGroup] {ApiGroup} singleton object containing parameters of API group. Helpful when you use the same
|
|
3446
|
-
* api for different providers to avoid hardcoding RPS inside each provider what can cause mistakes
|
|
3447
|
-
* @param [specificHeaders] {Object} contains specific keys (headers) and values (their content) if needed for this provider
|
|
3448
|
-
* @param [maxPageLength] {number} optional number of items per page if the request supports pagination
|
|
3449
|
-
*/
|
|
3450
|
-
constructor(endpoint, httpMethod, timeout, apiGroup, specificHeaders = {}, maxPageLength = Number.MAX_SAFE_INTEGER) {
|
|
3451
|
-
this.endpoint = endpoint;
|
|
3452
|
-
this.httpMethod = httpMethod != null ? httpMethod : "get";
|
|
3453
|
-
// TODO: [refactoring, critical] We have two timeouts for robust data retrieval - here and inside the service method call, need to remain the only
|
|
3454
|
-
this.timeout = timeout != null ? timeout : 10000;
|
|
3455
|
-
// TODO: [refactoring, critical] We need single place for all RPSes as we use them as hardcoded constants now inside different services
|
|
3456
|
-
this.apiGroup = apiGroup;
|
|
3457
|
-
this.maxPageLength = maxPageLength != null ? maxPageLength : Number.MAX_SAFE_INTEGER;
|
|
3458
|
-
this.niceFactor = 1;
|
|
3459
|
-
this.specificHeaders = specificHeaders != null ? specificHeaders : {};
|
|
3460
|
-
}
|
|
3461
|
-
getRps() {
|
|
3462
|
-
var _this$apiGroup$rps;
|
|
3463
|
-
return (_this$apiGroup$rps = this.apiGroup.rps) != null ? _this$apiGroup$rps : 2;
|
|
3464
|
-
}
|
|
3465
|
-
isRpsExceeded() {
|
|
3466
|
-
return this.apiGroup.isRpsExceeded();
|
|
3679
|
+
class TrackipIpAddressProvider extends ExternalApiProvider {
|
|
3680
|
+
constructor() {
|
|
3681
|
+
super("https://www.trackip.net/ip", "get", 15000, ApiGroups.TRACKIP);
|
|
3467
3682
|
}
|
|
3468
|
-
|
|
3469
|
-
|
|
3683
|
+
getDataByResponse(response, params = [], subRequestIndex = 0, iterationsData = []) {
|
|
3684
|
+
return response == null ? void 0 : response.data;
|
|
3470
3685
|
}
|
|
3471
|
-
|
|
3472
|
-
|
|
3686
|
+
}
|
|
3687
|
+
class IpifyV6IpAddressProvider extends ExternalApiProvider {
|
|
3688
|
+
constructor() {
|
|
3689
|
+
super("https://api6.ipify.org/?format=json", "get", 15000, ApiGroups.IPIFY);
|
|
3473
3690
|
}
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
* and another request for unconfirmed transactions. You should override this method to return true for such requests.
|
|
3478
|
-
*
|
|
3479
|
-
* @return {boolean} true if this provider requires several requests to retrieve the data
|
|
3480
|
-
*/
|
|
3481
|
-
doesRequireSubRequests() {
|
|
3482
|
-
return false;
|
|
3691
|
+
getDataByResponse(response, params = [], subRequestIndex = 0, iterationsData = []) {
|
|
3692
|
+
var _response$data2;
|
|
3693
|
+
return (response == null ? void 0 : response.data) && ((_response$data2 = response.data) == null ? void 0 : _response$data2.ip);
|
|
3483
3694
|
}
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
* @return {boolean} true if this provider requires several requests to retrieve the data
|
|
3489
|
-
*/
|
|
3490
|
-
doesSupportPagination() {
|
|
3491
|
-
return false;
|
|
3695
|
+
}
|
|
3696
|
+
class IpifyIpAddressProvider extends ExternalApiProvider {
|
|
3697
|
+
constructor() {
|
|
3698
|
+
super("https://api.ipify.org/?format=json", "get", 15000, ApiGroups.IPIFY);
|
|
3492
3699
|
}
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
*
|
|
3497
|
-
* @param params {any[]} params array passed to the RobustExternalAPICallerService
|
|
3498
|
-
* @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
|
|
3499
|
-
* @returns {string} query string to be concatenated with endpoint
|
|
3500
|
-
*/
|
|
3501
|
-
composeQueryString(params, subRequestIndex = 0) {
|
|
3502
|
-
return "";
|
|
3700
|
+
getDataByResponse(response, params = [], subRequestIndex = 0, iterationsData = []) {
|
|
3701
|
+
var _response$data3;
|
|
3702
|
+
return (response == null ? void 0 : response.data) && ((_response$data3 = response.data) == null ? void 0 : _response$data3.ip);
|
|
3503
3703
|
}
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
* @param params {any[]} params array passed to the RobustExternalAPICallerService
|
|
3509
|
-
* @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
|
|
3510
|
-
* @returns {string}
|
|
3511
|
-
*/
|
|
3512
|
-
composeBody(params, subRequestIndex = 0) {
|
|
3513
|
-
return "";
|
|
3704
|
+
}
|
|
3705
|
+
class WhatismyipaddressIpAddressProvider extends ExternalApiProvider {
|
|
3706
|
+
constructor() {
|
|
3707
|
+
super("http://bot.whatismyipaddress.com/", "get", 15000, ApiGroups.WHATISMYIPADDRESS);
|
|
3514
3708
|
}
|
|
3515
|
-
|
|
3516
|
-
/**
|
|
3517
|
-
* Extracts data from the response and returns it
|
|
3518
|
-
*
|
|
3519
|
-
* @param response {Object} HTTP response returned by provider
|
|
3520
|
-
* @param [params] {any[]} params array passed to the RobustExternalAPICallerService
|
|
3521
|
-
* @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
|
|
3522
|
-
* @param iterationsData {any[]} array of data retrieved from previous sub-requests
|
|
3523
|
-
* @returns {any}
|
|
3524
|
-
*/
|
|
3525
3709
|
getDataByResponse(response, params = [], subRequestIndex = 0, iterationsData = []) {
|
|
3526
|
-
return
|
|
3527
|
-
}
|
|
3528
|
-
|
|
3529
|
-
/**
|
|
3530
|
-
* Function changing the query string according to page number and previous response
|
|
3531
|
-
* Only for endpoints supporting pagination
|
|
3532
|
-
*
|
|
3533
|
-
* @param params {any[]} params array passed to the RobustExternalAPICallerService
|
|
3534
|
-
* @param previousResponse {Object} HTTP response returned by provider for previous call (previous page)
|
|
3535
|
-
* @param pageNumber {number} new page number. We count from 0. You need to manually increment with 1 if your
|
|
3536
|
-
* provider counts pages starting with 1
|
|
3537
|
-
* @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
|
|
3538
|
-
* @returns {any[]}
|
|
3539
|
-
*/
|
|
3540
|
-
changeQueryParametersForPageNumber(params, previousResponse, pageNumber, subRequestIndex = 0) {
|
|
3541
|
-
return params;
|
|
3710
|
+
return response == null ? void 0 : response.data;
|
|
3542
3711
|
}
|
|
3543
|
-
|
|
3712
|
+
}
|
|
3713
|
+
class IpAddressProvider {
|
|
3544
3714
|
/**
|
|
3545
|
-
*
|
|
3546
|
-
* Only for endpoints supporting pagination.
|
|
3715
|
+
* Returns current public IP address identified by one of external services.
|
|
3547
3716
|
*
|
|
3548
|
-
*
|
|
3549
|
-
*
|
|
3550
|
-
*
|
|
3551
|
-
* @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
|
|
3552
|
-
* @returns {boolean}
|
|
3553
|
-
*/
|
|
3554
|
-
checkWhetherResponseIsForLastPage(previousResponse, currentResponse, currentPageNumber, subRequestIndex = 0) {
|
|
3555
|
-
return true;
|
|
3556
|
-
}
|
|
3557
|
-
|
|
3558
|
-
/**
|
|
3559
|
-
* Resets the nice factor to default value
|
|
3560
|
-
*/
|
|
3561
|
-
resetNiceFactor() {
|
|
3562
|
-
this.niceFactor = 1;
|
|
3563
|
-
}
|
|
3564
|
-
|
|
3565
|
-
/**
|
|
3566
|
-
* Internal method used for requests requiring sub-requests.
|
|
3717
|
+
* It is easier than manual identification and also (as ip needed for server side to check it) it saves us from
|
|
3718
|
+
* issues related to changes of infrastructure configurations (like adding proxies etc.) so we should not configure
|
|
3719
|
+
* anything on server side to get correct client's IP.
|
|
3567
3720
|
*
|
|
3568
|
-
* @
|
|
3569
|
-
* @
|
|
3721
|
+
* @returns {Promise<String>} IP address
|
|
3722
|
+
* @throws {Error} if fails to retrieve IP address from all the services
|
|
3570
3723
|
*/
|
|
3571
|
-
|
|
3572
|
-
|
|
3724
|
+
static async getClientIpAddress() {
|
|
3725
|
+
try {
|
|
3726
|
+
return await this.externalIPAddressAPICaller.callExternalAPICached([], 7000);
|
|
3727
|
+
} catch (e) {
|
|
3728
|
+
improveAndRethrow(e, "getClientIpAddress");
|
|
3729
|
+
}
|
|
3573
3730
|
}
|
|
3574
3731
|
}
|
|
3732
|
+
IpAddressProvider.externalIPAddressAPICaller = new CachedRobustExternalApiCallerService("externalIPAddressAPICaller", new Cache(EventBusInstance), [new BigdatacloudIpAddressProvider(), new TrackipIpAddressProvider(), new IpifyV6IpAddressProvider(), new IpifyIpAddressProvider(), new WhatismyipaddressIpAddressProvider()], 300000);
|
|
3575
3733
|
|
|
3576
3734
|
/**
|
|
3577
|
-
*
|
|
3578
|
-
*
|
|
3579
|
-
* api group. So this concept allows to use it for exact ExternalApiProvider and make sure that you use the same
|
|
3580
|
-
* RPS value and make decisions on base of the same timestamp of last call to the API group owner.
|
|
3735
|
+
* Utils class needed to perform cancelling of axios request inside some process.
|
|
3736
|
+
* Provides cancel state and axios token for HTTP requests
|
|
3581
3737
|
*/
|
|
3582
|
-
class
|
|
3583
|
-
constructor(
|
|
3584
|
-
this.
|
|
3585
|
-
this.
|
|
3586
|
-
this.lastCalledTimestamp = null;
|
|
3587
|
-
this.backendProxyIdGenerator = backendProxyIdGenerator;
|
|
3738
|
+
class CancelProcessing {
|
|
3739
|
+
constructor() {
|
|
3740
|
+
this._cancelToken = axios.CancelToken.source();
|
|
3741
|
+
this._isCanceled = false;
|
|
3588
3742
|
}
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3743
|
+
cancel() {
|
|
3744
|
+
this._isCanceled = true;
|
|
3745
|
+
this._cancelToken.cancel();
|
|
3592
3746
|
}
|
|
3593
|
-
|
|
3594
|
-
this.
|
|
3747
|
+
isCanceled() {
|
|
3748
|
+
return this._isCanceled;
|
|
3749
|
+
}
|
|
3750
|
+
getToken() {
|
|
3751
|
+
return this._cancelToken.token;
|
|
3752
|
+
}
|
|
3753
|
+
static instance() {
|
|
3754
|
+
return new CancelProcessing();
|
|
3595
3755
|
}
|
|
3596
3756
|
}
|
|
3597
|
-
const ApiGroups = {
|
|
3598
|
-
/**
|
|
3599
|
-
* Currently we use free version of etherscan provider with 0.2 RPS. But we have API key with 100k requests free
|
|
3600
|
-
* per month. So we can add it if not enough current RPS.
|
|
3601
|
-
*/
|
|
3602
|
-
ETHERSCAN: new ApiGroup("etherscan", 0.17),
|
|
3603
|
-
// Actually 0.2 but fails sometime, so we use smaller
|
|
3604
|
-
ALCHEMY: new ApiGroup("alchemy", 0.3, networkKey => `alchemy-${networkKey}`),
|
|
3605
|
-
BLOCKSTREAM: new ApiGroup("blockstream", 0.2),
|
|
3606
|
-
BLOCKCHAIN_INFO: new ApiGroup("blockchain.info", 1),
|
|
3607
|
-
BLOCKNATIVE: new ApiGroup("blocknative", 0.5),
|
|
3608
|
-
ETHGASSTATION: new ApiGroup("ethgasstation", 0.5),
|
|
3609
|
-
TRONGRID: new ApiGroup("trongrid", 0.3, networkKey => `trongrid-${networkKey}`),
|
|
3610
|
-
TRONSCAN: new ApiGroup("tronscan", 0.3),
|
|
3611
|
-
GETBLOCK: new ApiGroup("getblock", 0.3),
|
|
3612
|
-
COINCAP: new ApiGroup("coincap", 0.5),
|
|
3613
|
-
// 200 per minute without API key
|
|
3614
|
-
COINGECKO: new ApiGroup("coingecko", 0.9),
|
|
3615
|
-
// actually 0.13-0.5 according to the docs but we use smaller due to expirienced frequent abuses
|
|
3616
|
-
MESSARI: new ApiGroup("messari", 0.2),
|
|
3617
|
-
BTCCOM: new ApiGroup("btccom", 0.2),
|
|
3618
|
-
BITAPS: new ApiGroup("bitaps", 0.25),
|
|
3619
|
-
// Docs say that RPS is 3 but using it causes frequent 429 HTTP errors
|
|
3620
|
-
CEX: new ApiGroup("cex", 0.5),
|
|
3621
|
-
// Just assumption for RPS
|
|
3622
|
-
BIGDATACLOUD: new ApiGroup("bigdatacloud", 1),
|
|
3623
|
-
// Just assumption for RPS
|
|
3624
|
-
TRACKIP: new ApiGroup("trackip", 1),
|
|
3625
|
-
// Just assumption for RPS
|
|
3626
|
-
IPIFY: new ApiGroup("ipify", 1),
|
|
3627
|
-
// Just assumption for RPS
|
|
3628
|
-
WHATISMYIPADDRESS: new ApiGroup("whatismyipaddress", 1),
|
|
3629
|
-
// Just assumption for RPS
|
|
3630
|
-
EXCHANGERATE: new ApiGroup("exchangerate", 1),
|
|
3631
|
-
// Just assumption for RPS
|
|
3632
|
-
FRANKFURTER: new ApiGroup("frankfurter", 1),
|
|
3633
|
-
// Just assumption for RPS
|
|
3634
|
-
BITGO: new ApiGroup("bitgo", 1),
|
|
3635
|
-
// Just assumption for RPS
|
|
3636
|
-
BITCOINER: new ApiGroup("bitcoiner", 1),
|
|
3637
|
-
// Just assumption for RPS
|
|
3638
|
-
BITCORE: new ApiGroup("bitcore", 1),
|
|
3639
|
-
// Just assumption for RPS
|
|
3640
|
-
// BLOCKCHAIR: new ApiGroup("blockchair", 0.04), // this provider require API key for commercial use (10usd 10000 reqs), we will add it later
|
|
3641
|
-
MEMPOOL: new ApiGroup("mempool", 0.2) // Just assumption for RPS
|
|
3642
|
-
};
|
|
3643
3757
|
|
|
3644
3758
|
class ExistingSwap {
|
|
3645
3759
|
/**
|
|
@@ -3649,6 +3763,7 @@ class ExistingSwap {
|
|
|
3649
3763
|
* @param expiresAt {number}
|
|
3650
3764
|
* @param confirmations {number}
|
|
3651
3765
|
* @param rate {string}
|
|
3766
|
+
* @param fixed {boolean}
|
|
3652
3767
|
* @param refundAddress {string}
|
|
3653
3768
|
* @param payToAddress {string}
|
|
3654
3769
|
* @param fromCoin {Coin}
|
|
@@ -3665,7 +3780,7 @@ class ExistingSwap {
|
|
|
3665
3780
|
* @param toExtraId {string}
|
|
3666
3781
|
* @param refundExtraId {string}
|
|
3667
3782
|
*/
|
|
3668
|
-
constructor(swapId, status, createdAt, expiresAt, confirmations, rate, refundAddress, payToAddress, fromCoin, fromAmount, fromTransactionId, fromTransactionLink, toCoin, toAmount, toTransactionId, toTransactionLink, toAddress,
|
|
3783
|
+
constructor(swapId, status, createdAt, expiresAt, confirmations, rate, fixed, refundAddress, payToAddress, fromCoin, fromAmount, fromTransactionId, fromTransactionLink, toCoin, toAmount, toTransactionId, toTransactionLink, toAddress,
|
|
3669
3784
|
// TODO: [refactoring, moderate] toAddress is not quite clear. How about recipientAddress? task_id=0815a111c99543b78d374217eadbde4f
|
|
3670
3785
|
partner, fromExtraId, toExtraId, refundExtraId) {
|
|
3671
3786
|
this.swapId = swapId;
|
|
@@ -3674,6 +3789,7 @@ class ExistingSwap {
|
|
|
3674
3789
|
this.expiresAt = expiresAt;
|
|
3675
3790
|
this.confirmations = confirmations;
|
|
3676
3791
|
this.rate = rate;
|
|
3792
|
+
this.fixed = fixed;
|
|
3677
3793
|
this.refundAddress = refundAddress;
|
|
3678
3794
|
this.payToAddress = payToAddress;
|
|
3679
3795
|
this.fromCoin = fromCoin;
|
|
@@ -3700,6 +3816,7 @@ class ExistingSwapWithFiatData extends ExistingSwap {
|
|
|
3700
3816
|
* @param expiresAt {number}
|
|
3701
3817
|
* @param confirmations {number}
|
|
3702
3818
|
* @param rate {string}
|
|
3819
|
+
* @param fixed {boolean}
|
|
3703
3820
|
* @param refundAddress {string}
|
|
3704
3821
|
* @param payToAddress {string}
|
|
3705
3822
|
* @param fromCoin {Coin}
|
|
@@ -3720,8 +3837,8 @@ class ExistingSwapWithFiatData extends ExistingSwap {
|
|
|
3720
3837
|
* @param fiatCurrencyCode {string}
|
|
3721
3838
|
* @param fiatCurrencyDecimals {number}
|
|
3722
3839
|
*/
|
|
3723
|
-
constructor(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) {
|
|
3724
|
-
super(swapId, status, createdAt, expiresAt, confirmations, rate, refundAddress, payToAddress, fromCoin, fromAmount, fromTransactionId, fromTransactionLink, toCoin, toAmount, toTransactionId, toTransactionLink, toAddress, partner, fromExtraId, toExtraId, refundExtraId);
|
|
3840
|
+
constructor(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) {
|
|
3841
|
+
super(swapId, status, createdAt, expiresAt, confirmations, rate, fixed, refundAddress, payToAddress, fromCoin, fromAmount, fromTransactionId, fromTransactionLink, toCoin, toAmount, toTransactionId, toTransactionLink, toAddress, partner, fromExtraId, toExtraId, refundExtraId);
|
|
3725
3842
|
this.fromAmountFiat = fromAmountFiat;
|
|
3726
3843
|
this.toAmountFiat = toAmountFiat;
|
|
3727
3844
|
this.fiatCurrencyCode = fiatCurrencyCode;
|
|
@@ -3737,7 +3854,7 @@ class ExistingSwapWithFiatData extends ExistingSwap {
|
|
|
3737
3854
|
* @return {ExistingSwapWithFiatData}
|
|
3738
3855
|
*/
|
|
3739
3856
|
static fromExistingSwap(existingSwap, fromAmountFiat, toAmountFiat, fiatCurrencyCode, fiatCurrencyDecimals) {
|
|
3740
|
-
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);
|
|
3857
|
+
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);
|
|
3741
3858
|
}
|
|
3742
3859
|
}
|
|
3743
3860
|
|
|
@@ -3754,8 +3871,9 @@ class BaseSwapCreationInfo {
|
|
|
3754
3871
|
* @param max {string}
|
|
3755
3872
|
* @param fiatMax {number}
|
|
3756
3873
|
* @param durationMinutesRange {string}
|
|
3874
|
+
* @param fixed {boolean}
|
|
3757
3875
|
*/
|
|
3758
|
-
constructor(fromCoin, toCoin, fromAmountCoins, toAmountCoins, rate, rawSwapData, min, fiatMin, max, fiatMax, durationMinutesRange) {
|
|
3876
|
+
constructor(fromCoin, toCoin, fromAmountCoins, toAmountCoins, rate, rawSwapData, min, fiatMin, max, fiatMax, durationMinutesRange, fixed) {
|
|
3759
3877
|
this.fromCoin = fromCoin;
|
|
3760
3878
|
this.toCoin = toCoin;
|
|
3761
3879
|
this.fromAmountCoins = fromAmountCoins;
|
|
@@ -3767,6 +3885,7 @@ class BaseSwapCreationInfo {
|
|
|
3767
3885
|
this.max = max;
|
|
3768
3886
|
this.fiatMax = fiatMax;
|
|
3769
3887
|
this.durationMinutesRange = durationMinutesRange;
|
|
3888
|
+
this.fixed = fixed;
|
|
3770
3889
|
}
|
|
3771
3890
|
}
|
|
3772
3891
|
|
|
@@ -3845,6 +3964,7 @@ class SwapProvider {
|
|
|
3845
3964
|
* @param fromCoin {Coin}
|
|
3846
3965
|
* @param toCoin {Coin}
|
|
3847
3966
|
* @param amountCoins {string}
|
|
3967
|
+
* @param [fixed=false] {boolean|null} null means fixed or float doesn't matter
|
|
3848
3968
|
* @param [fromCoinToUsdRate=null] pass if you want to increase the min amount returned
|
|
3849
3969
|
* by provider with some fixed "insurance" amount to cover min amount fluctuations.
|
|
3850
3970
|
* @return {Promise<({
|
|
@@ -3860,15 +3980,16 @@ class SwapProvider {
|
|
|
3860
3980
|
* greatestMax: (string|null),
|
|
3861
3981
|
* rate: (string|null),
|
|
3862
3982
|
* durationMinutesRange: string,
|
|
3983
|
+
* fixed: boolean,
|
|
3863
3984
|
* [rawSwapData]: Object
|
|
3864
3985
|
* })>}
|
|
3865
3986
|
*/
|
|
3866
|
-
async getSwapInfo(fromCoin, toCoin, amountCoins, fromCoinToUsdRate = null) {
|
|
3987
|
+
async getSwapInfo(fromCoin, toCoin, amountCoins, fixed = false, fromCoinToUsdRate = null) {
|
|
3867
3988
|
throw new Error("Not implemented in base");
|
|
3868
3989
|
}
|
|
3869
3990
|
|
|
3870
3991
|
/**
|
|
3871
|
-
* For fail result we return one of SwapProvider.CREATION_FAIL_REASONS or SwapProvider.COMMON_ERRORS
|
|
3992
|
+
* For fail result we return one of SwapProvider.CREATION_FAIL_REASONS or SwapProvider.COMMON_ERRORS.*
|
|
3872
3993
|
*
|
|
3873
3994
|
* @param fromCoin {Coin}
|
|
3874
3995
|
* @param toCoin {Coin}
|
|
@@ -3877,6 +3998,7 @@ class SwapProvider {
|
|
|
3877
3998
|
* @param refundAddress {string}
|
|
3878
3999
|
* @param rawSwapData {Object|null}
|
|
3879
4000
|
* @param clientIpAddress {string}
|
|
4001
|
+
* @param fixed {boolean}
|
|
3880
4002
|
* @param [toCurrencyExtraId=""] {string} optional extra ID
|
|
3881
4003
|
* @param [refundExtraId=""] {string} optional extra ID for refund address
|
|
3882
4004
|
* @return {Promise<({
|
|
@@ -3889,14 +4011,15 @@ class SwapProvider {
|
|
|
3889
4011
|
* toAmount: string,
|
|
3890
4012
|
* toAddress: string,
|
|
3891
4013
|
* rate: string,
|
|
3892
|
-
* fromCurrencyExtraId: string|undefined
|
|
4014
|
+
* fromCurrencyExtraId: string|undefined,
|
|
4015
|
+
* fixed: boolean
|
|
3893
4016
|
* }|{
|
|
3894
4017
|
* result: false,
|
|
3895
4018
|
* reason: string,
|
|
3896
4019
|
* partner: string
|
|
3897
4020
|
* })>}
|
|
3898
4021
|
*/
|
|
3899
|
-
async createSwap(fromCoin, toCoin, amount, toAddress, refundAddress, rawSwapData
|
|
4022
|
+
async createSwap(fromCoin, toCoin, amount, toAddress, refundAddress, rawSwapData, clientIpAddress, fixed, toCurrencyExtraId = "", refundExtraId = "") {
|
|
3900
4023
|
throw new Error("Not implemented in base");
|
|
3901
4024
|
}
|
|
3902
4025
|
|
|
@@ -3942,7 +4065,9 @@ SwapProvider.COMMON_ERRORS = {
|
|
|
3942
4065
|
SwapProvider.NO_SWAPS_REASONS = {
|
|
3943
4066
|
TOO_LOW: "tooLow",
|
|
3944
4067
|
TOO_HIGH: "tooHigh",
|
|
3945
|
-
NOT_SUPPORTED: "notSupported"
|
|
4068
|
+
NOT_SUPPORTED: "notSupported",
|
|
4069
|
+
NO_FIXED_BUT_HAVE_FLOATING: "noFixedButHaveFloating",
|
|
4070
|
+
NO_FLOATING_BUT_HAVE_FIXED: "noFloatingButHaveFixed"
|
|
3946
4071
|
};
|
|
3947
4072
|
SwapProvider.CREATION_FAIL_REASONS = {
|
|
3948
4073
|
RETRIABLE_FAIL: "retriableFail"
|
|
@@ -4093,7 +4218,6 @@ class SwapspaceSwapProvider extends SwapProvider {
|
|
|
4093
4218
|
const ticker = `${code}${code === network ? "" : network}`;
|
|
4094
4219
|
const defaultDecimalPlacesForCoinNotSupportedOOB = 8;
|
|
4095
4220
|
const defaultMinConfirmationsForCoinNotSupportedOOB = 1;
|
|
4096
|
-
// TODO: [dev] maybe we should recognize standard protocols?
|
|
4097
4221
|
coin = new Coin(item.name, ticker, code, defaultDecimalPlacesForCoinNotSupportedOOB, null, "", null, null, defaultMinConfirmationsForCoinNotSupportedOOB, null, [], 60000, null,
|
|
4098
4222
|
// We cannot recognize blockchain from swapspace data
|
|
4099
4223
|
code !== network ? new Protocol(network) : null, item.contractAddress || null, false);
|
|
@@ -4106,7 +4230,7 @@ class SwapspaceSwapProvider extends SwapProvider {
|
|
|
4106
4230
|
network: item.network,
|
|
4107
4231
|
hasExtraId: item.hasExtraId,
|
|
4108
4232
|
extraIdName: item.extraIdName,
|
|
4109
|
-
isPopular: !!
|
|
4233
|
+
isPopular: !!item.popular,
|
|
4110
4234
|
iconURL: item.icon ? `https://storage.swapspace.co${item.icon}` : FALLBACK_ICON_URL,
|
|
4111
4235
|
deposit: (_item$deposit = item.deposit) != null ? _item$deposit : false,
|
|
4112
4236
|
withdrawal: (_item$withdrawal = item.withdrawal) != null ? _item$withdrawal : false,
|
|
@@ -4156,7 +4280,7 @@ class SwapspaceSwapProvider extends SwapProvider {
|
|
|
4156
4280
|
};
|
|
4157
4281
|
}
|
|
4158
4282
|
Logger.log("Loading USDT->coin rate as not found in cache:", coin == null ? void 0 : coin.ticker);
|
|
4159
|
-
const result = await this.getSwapInfo(usdtTrc20, coin, "5000");
|
|
4283
|
+
const result = await this.getSwapInfo(usdtTrc20, coin, "5000", false);
|
|
4160
4284
|
if (!result.result) {
|
|
4161
4285
|
return {
|
|
4162
4286
|
result: false
|
|
@@ -4164,7 +4288,7 @@ class SwapspaceSwapProvider extends SwapProvider {
|
|
|
4164
4288
|
}
|
|
4165
4289
|
|
|
4166
4290
|
// This calculation is not precise as we cannot recognize the actual fee and network fee. Just approximate.
|
|
4167
|
-
const standardSwapspaceFeeMultiplier = 1.004; // usually 0.
|
|
4291
|
+
const standardSwapspaceFeeMultiplier = 1.004; // fee is usually 0.4%
|
|
4168
4292
|
const rate = BigNumber(1).div(BigNumber(result.rate).times(standardSwapspaceFeeMultiplier)).toString();
|
|
4169
4293
|
this._cache.put("swapspace_usdt_rate_" + coin.ticker, rate, 15 * 60000 // 15 minutes
|
|
4170
4294
|
);
|
|
@@ -4185,12 +4309,12 @@ class SwapspaceSwapProvider extends SwapProvider {
|
|
|
4185
4309
|
improveAndRethrow(e, "getCoinByTickerIfPresent");
|
|
4186
4310
|
}
|
|
4187
4311
|
}
|
|
4188
|
-
async getSwapInfo(fromCoin, toCoin, amountCoins, fromCoinToUsdRate = null) {
|
|
4312
|
+
async getSwapInfo(fromCoin, toCoin, amountCoins, fixed = false, fromCoinToUsdRate = null) {
|
|
4189
4313
|
const loggerSource = "getSwapInfo";
|
|
4190
4314
|
try {
|
|
4191
|
-
var _response$data;
|
|
4192
|
-
if (!(fromCoin instanceof Coin) || !(toCoin instanceof Coin) || typeof amountCoins !== "string" || BigNumber(amountCoins).lt("0")) {
|
|
4193
|
-
throw new Error(`Wrong input params: ${amountCoins} ${fromCoin.ticker} -> ${toCoin.ticker}
|
|
4315
|
+
var _response$data, _exchangesSupportingT;
|
|
4316
|
+
if (!(fromCoin instanceof Coin) || !(toCoin instanceof Coin) || typeof amountCoins !== "string" || BigNumber(amountCoins).lt("0") || fixed !== null && typeof fixed !== "boolean") {
|
|
4317
|
+
throw new Error(`Wrong input params: ${amountCoins} ${fromCoin.ticker} -> ${toCoin.ticker}, ${fromCoin instanceof Coin}, ${toCoin instanceof Coin}, ${typeof fixed} ${fixed}`);
|
|
4194
4318
|
}
|
|
4195
4319
|
const fromCoinSwapspaceDetails = this._supportedCoins.find(i => {
|
|
4196
4320
|
var _i$coin;
|
|
@@ -4213,12 +4337,22 @@ class SwapspaceSwapProvider extends SwapProvider {
|
|
|
4213
4337
|
* cached rate values stored in swapspace cache. Their support says they store at most for 30 sec.
|
|
4214
4338
|
* But we are better off using the most actual rates.
|
|
4215
4339
|
*/
|
|
4216
|
-
const response = await axios.get(`${this._URL}/api/v2/amounts?fromCurrency=${fromCoinSwapspaceDetails.code}&fromNetwork=${fromCoinSwapspaceDetails.network}&toNetwork=${toCoinSwapspaceDetails.network}&toCurrency=${toCoinSwapspaceDetails.code}&amount=${amountCoins}&
|
|
4340
|
+
const response = await axios.get(`${this._URL}/api/v2/amounts?fromCurrency=${fromCoinSwapspaceDetails.code}&fromNetwork=${fromCoinSwapspaceDetails.network}&toNetwork=${toCoinSwapspaceDetails.network}&toCurrency=${toCoinSwapspaceDetails.code}&amount=${amountCoins}&estimated=false`);
|
|
4217
4341
|
Logger.log(`Retrieved ${response == null || (_response$data = response.data) == null ? void 0 : _response$data.length} options`, loggerSource);
|
|
4218
4342
|
const options = Array.isArray(response.data) ? response.data : [];
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
if (
|
|
4343
|
+
let exchangesSupportingThePairDespiteFixedOrFloating = options.filter(exchange => (exchange == null ? void 0 : exchange.exists) && !BANNED_PARTNERS.find(bannedPartner => bannedPartner === (exchange == null ? void 0 : exchange.partner)) && ((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));
|
|
4344
|
+
let exchangesSupportingThePair = exchangesSupportingThePairDespiteFixedOrFloating;
|
|
4345
|
+
if (fixed != null) {
|
|
4346
|
+
exchangesSupportingThePair = exchangesSupportingThePairDespiteFixedOrFloating.filter(option => option.fixed === fixed);
|
|
4347
|
+
}
|
|
4348
|
+
Logger.log(`${(_exchangesSupportingT = exchangesSupportingThePair) == null ? void 0 : _exchangesSupportingT.length} of them have exist=true`, loggerSource);
|
|
4349
|
+
if (exchangesSupportingThePair.length === 0) {
|
|
4350
|
+
if (exchangesSupportingThePairDespiteFixedOrFloating.length > 0 && fixed !== null) {
|
|
4351
|
+
return {
|
|
4352
|
+
result: false,
|
|
4353
|
+
reason: fixed ? SwapProvider.NO_SWAPS_REASONS.NO_FIXED_BUT_HAVE_FLOATING : SwapProvider.NO_SWAPS_REASONS.NO_FLOATING_BUT_HAVE_FIXED
|
|
4354
|
+
};
|
|
4355
|
+
}
|
|
4222
4356
|
return {
|
|
4223
4357
|
result: false,
|
|
4224
4358
|
reason: SwapProvider.NO_SWAPS_REASONS.NOT_SUPPORTED
|
|
@@ -4281,6 +4415,7 @@ class SwapspaceSwapProvider extends SwapProvider {
|
|
|
4281
4415
|
greatestMax: greatestMax,
|
|
4282
4416
|
rate: rate != null ? AmountUtils.trim(rate, this._maxRateDigits) : null,
|
|
4283
4417
|
durationMinutesRange: (_bestOpt$duration = bestOpt.duration) != null ? _bestOpt$duration : null,
|
|
4418
|
+
fixed: bestOpt.fixed,
|
|
4284
4419
|
rawSwapData: bestOpt
|
|
4285
4420
|
};
|
|
4286
4421
|
}
|
|
@@ -4304,17 +4439,18 @@ class SwapspaceSwapProvider extends SwapProvider {
|
|
|
4304
4439
|
improveAndRethrow(e, loggerSource);
|
|
4305
4440
|
}
|
|
4306
4441
|
}
|
|
4307
|
-
async createSwap(fromCoin, toCoin, amount, toAddress, refundAddress, rawSwapData, clientIpAddress, toCurrencyExtraId = "", refundExtraId = "") {
|
|
4442
|
+
async createSwap(fromCoin, toCoin, amount, toAddress, refundAddress, rawSwapData, clientIpAddress, fixed, toCurrencyExtraId = "", refundExtraId = "") {
|
|
4308
4443
|
const loggerSource = "createSwap";
|
|
4309
4444
|
const partner = rawSwapData == null ? void 0 : rawSwapData.partner;
|
|
4310
4445
|
try {
|
|
4311
|
-
if (!(fromCoin instanceof Coin) || !(toCoin instanceof Coin) || typeof amount !== "string" || typeof toAddress !== "string" || typeof refundAddress !== "string") {
|
|
4312
|
-
throw new Error(`Invalid input: ${fromCoin} ${toCoin} ${amount} ${toAddress} ${refundAddress}`);
|
|
4446
|
+
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) {
|
|
4447
|
+
throw new Error(`Invalid input: ${fromCoin} ${toCoin} ${amount} ${toAddress} ${refundAddress} ${clientIpAddress == null ? void 0 : clientIpAddress.length} ${fixed}`);
|
|
4313
4448
|
}
|
|
4314
4449
|
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
|
|
4315
4450
|
) {
|
|
4316
4451
|
throw new Error(`Invalid raw swap data: ${safeStringify(rawSwapData)}`);
|
|
4317
4452
|
}
|
|
4453
|
+
const [fromCurrencyHasExtraId, toCurrencyHasExtraId] = this._supportedCoins.reduce((prev, coinData) => [coinData.coin.ticker === fromCoin.ticker ? coinData.hasExtraId : prev[0], coinData.coin.ticker === toCoin.ticker ? coinData.hasExtraId : prev[1]], [false, false]);
|
|
4318
4454
|
await this._fetchSupportedCurrenciesIfNeeded();
|
|
4319
4455
|
const requestData = {
|
|
4320
4456
|
partner: partner,
|
|
@@ -4324,10 +4460,10 @@ class SwapspaceSwapProvider extends SwapProvider {
|
|
|
4324
4460
|
toNetwork: rawSwapData == null ? void 0 : rawSwapData.toNetwork,
|
|
4325
4461
|
address: toAddress,
|
|
4326
4462
|
amount: amount,
|
|
4327
|
-
fixed:
|
|
4328
|
-
extraId: toCurrencyExtraId != null ? toCurrencyExtraId : "",
|
|
4329
|
-
refundExtraId: refundExtraId != null ? refundExtraId : "",
|
|
4463
|
+
fixed: fixed,
|
|
4464
|
+
extraId: toCurrencyHasExtraId ? toCurrencyExtraId != null ? toCurrencyExtraId : "" : "",
|
|
4330
4465
|
// This param is not documented. But the refund is usually manual so this is not critical.
|
|
4466
|
+
refundExtraId: fromCurrencyHasExtraId ? refundExtraId != null ? refundExtraId : "" : "",
|
|
4331
4467
|
rateId: rawSwapData == null ? void 0 : rawSwapData.id,
|
|
4332
4468
|
userIp: clientIpAddress,
|
|
4333
4469
|
refund: refundAddress
|
|
@@ -4338,7 +4474,7 @@ class SwapspaceSwapProvider extends SwapProvider {
|
|
|
4338
4474
|
Logger.log(`Creation result ${safeStringify(result)}`, loggerSource);
|
|
4339
4475
|
if (result != null && result.id) {
|
|
4340
4476
|
var _result$from, _result$from2, _result$to, _result$to2, _result$from4, _result$from5, _result$to4, _result$to5, _result$from$extraId, _result$from6;
|
|
4341
|
-
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}`);
|
|
4477
|
+
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}`);
|
|
4342
4478
|
/* We use the returned rate preferably but if the retrieved
|
|
4343
4479
|
* rate 0/null/undefined we calculate it manually */
|
|
4344
4480
|
let rate = result.rate;
|
|
@@ -4358,7 +4494,8 @@ class SwapspaceSwapProvider extends SwapProvider {
|
|
|
4358
4494
|
toAmount: AmountUtils.trim(result == null || (_result$to4 = result.to) == null ? void 0 : _result$to4.amount, toCoin.digits),
|
|
4359
4495
|
toAddress: result == null || (_result$to5 = result.to) == null ? void 0 : _result$to5.address,
|
|
4360
4496
|
fromCurrencyExtraId: (_result$from$extraId = result == null || (_result$from6 = result.from) == null ? void 0 : _result$from6.extraId) != null ? _result$from$extraId : "",
|
|
4361
|
-
rate: AmountUtils.trim(rate, this._maxRateDigits)
|
|
4497
|
+
rate: AmountUtils.trim(rate, this._maxRateDigits),
|
|
4498
|
+
fixed: result.fixed
|
|
4362
4499
|
};
|
|
4363
4500
|
}
|
|
4364
4501
|
const errorMessage = `Swap creation succeeded but the response is wrong: ${safeStringify(response)}`;
|
|
@@ -4447,7 +4584,7 @@ class SwapspaceSwapProvider extends SwapProvider {
|
|
|
4447
4584
|
const status = this._mapSwapspaceStatusToRabbitStatus(swap.status, isExpiredByTime);
|
|
4448
4585
|
const toDigits = status === SwapProvider.SWAP_STATUSES.REFUNDED ? fromCoin.digits : toCoin.digits;
|
|
4449
4586
|
const addressToSendCoinsToSwapspace = swap.from.address;
|
|
4450
|
-
return new ExistingSwap(swapIds[index], status, toUtcTimestamp(swap.timestamps.createdAt), expiresAt, swap.confirmations, AmountUtils.trim(swap.rate, this._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);
|
|
4587
|
+
return new ExistingSwap(swapIds[index], status, toUtcTimestamp(swap.timestamps.createdAt), expiresAt, swap.confirmations, AmountUtils.trim(swap.rate, this._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);
|
|
4451
4588
|
}).flat();
|
|
4452
4589
|
Logger.log(`Swap details result ${safeStringify(swaps)}`, loggerSource);
|
|
4453
4590
|
return {
|
|
@@ -4744,6 +4881,7 @@ class PublicSwapService {
|
|
|
4744
4881
|
* @param fromCoin {Coin}
|
|
4745
4882
|
* @param toCoin {Coin}
|
|
4746
4883
|
* @param fromAmountCoins {string}
|
|
4884
|
+
* @param [fixed=false] {boolean|null} null means fixed or float doesn't matter
|
|
4747
4885
|
* @param [withoutFiat=false] {boolean} pass true if you don't need the fiat equivalent - this will diminish requests count
|
|
4748
4886
|
* @return {Promise<{
|
|
4749
4887
|
* result: false,
|
|
@@ -4758,12 +4896,12 @@ class PublicSwapService {
|
|
|
4758
4896
|
* swapCreationInfo: BaseSwapCreationInfo
|
|
4759
4897
|
* }>}
|
|
4760
4898
|
*/
|
|
4761
|
-
async getPublicSwapDetails(fromCoin, toCoin, fromAmountCoins, withoutFiat = false) {
|
|
4899
|
+
async getPublicSwapDetails(fromCoin, toCoin, fromAmountCoins, fixed = false, withoutFiat = false) {
|
|
4762
4900
|
const loggerSource = "getPublicSwapDetails";
|
|
4763
4901
|
try {
|
|
4764
4902
|
var _await$this$_swapProv, _await$this$_swapProv2, _result$swapCreationI, _result$swapCreationI2;
|
|
4765
4903
|
const coinUsdtRate = withoutFiat ? null : (_await$this$_swapProv = (_await$this$_swapProv2 = await this._swapProvider.getCoinToUSDTRate(fromCoin)) == null ? void 0 : _await$this$_swapProv2.rate) != null ? _await$this$_swapProv : null;
|
|
4766
|
-
const details = await this._swapProvider.getSwapInfo(fromCoin, toCoin, fromAmountCoins, coinUsdtRate);
|
|
4904
|
+
const details = await this._swapProvider.getSwapInfo(fromCoin, toCoin, fromAmountCoins, fixed, coinUsdtRate);
|
|
4767
4905
|
const min = details.result ? details.min : details.smallestMin;
|
|
4768
4906
|
const max = details.result ? details.max : details.greatestMax;
|
|
4769
4907
|
let fiatMin = null,
|
|
@@ -4789,9 +4927,15 @@ class PublicSwapService {
|
|
|
4789
4927
|
};
|
|
4790
4928
|
};
|
|
4791
4929
|
if (!details.result) {
|
|
4792
|
-
if ((details == null ? void 0 : details.reason) === SwapProvider.NO_SWAPS_REASONS.NOT_SUPPORTED)
|
|
4930
|
+
if ((details == null ? void 0 : details.reason) === SwapProvider.NO_SWAPS_REASONS.NOT_SUPPORTED) {
|
|
4931
|
+
return composeFailResult(PublicSwapService.PUBLIC_SWAP_DETAILS_FAIL_REASONS.PAIR_NOT_SUPPORTED);
|
|
4932
|
+
} else if ((details == null ? void 0 : details.reason) === SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED) {
|
|
4793
4933
|
SwapUtils.safeHandleRequestsLimitExceeding();
|
|
4794
4934
|
return composeFailResult(PublicSwapService.PUBLIC_SWAPS_COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED);
|
|
4935
|
+
} else if ((details == null ? void 0 : details.reason) === SwapProvider.NO_SWAPS_REASONS.NO_FLOATING_BUT_HAVE_FIXED) {
|
|
4936
|
+
return composeFailResult(PublicSwapService.PUBLIC_SWAP_DETAILS_FAIL_REASONS.NO_FLOATING_BUT_HAVE_FIXED_PUBLIC_SWAP_OPTION);
|
|
4937
|
+
} else if ((details == null ? void 0 : details.reason) === SwapProvider.NO_SWAPS_REASONS.NO_FIXED_BUT_HAVE_FLOATING) {
|
|
4938
|
+
return composeFailResult(PublicSwapService.PUBLIC_SWAP_DETAILS_FAIL_REASONS.NO_FIXED_BUT_HAVE_FLOATING_PUBLIC_SWAP_OPTION);
|
|
4795
4939
|
}
|
|
4796
4940
|
}
|
|
4797
4941
|
const fromAmountBigNumber = BigNumber(fromAmountCoins);
|
|
@@ -4803,7 +4947,7 @@ class PublicSwapService {
|
|
|
4803
4947
|
const toAmountCoins = AmountUtils.trim(fromAmountBigNumber.times(details.rate), fromCoin.digits);
|
|
4804
4948
|
const result = {
|
|
4805
4949
|
result: true,
|
|
4806
|
-
swapCreationInfo: new BaseSwapCreationInfo(fromCoin, toCoin, fromAmountCoins, toAmountCoins, details.rate, details.rawSwapData, min, fiatMin, max, fiatMax, details.durationMinutesRange)
|
|
4950
|
+
swapCreationInfo: new BaseSwapCreationInfo(fromCoin, toCoin, fromAmountCoins, toAmountCoins, details.rate, details.rawSwapData, min, fiatMin, max, fiatMax, details.durationMinutesRange, details.fixed)
|
|
4807
4951
|
};
|
|
4808
4952
|
Logger.log(`Result: ${safeStringify({
|
|
4809
4953
|
result: result.result,
|
|
@@ -4844,7 +4988,8 @@ class PublicSwapService {
|
|
|
4844
4988
|
* fromCoin: Coin,
|
|
4845
4989
|
* rate: string,
|
|
4846
4990
|
* swapId: string,
|
|
4847
|
-
* fromCurrencyExtraId: string
|
|
4991
|
+
* fromCurrencyExtraId: string,
|
|
4992
|
+
* fixed: boolean
|
|
4848
4993
|
* }|{
|
|
4849
4994
|
* result: false,
|
|
4850
4995
|
* reason: string
|
|
@@ -4861,7 +5006,7 @@ class PublicSwapService {
|
|
|
4861
5006
|
fromCoin: swapCreationInfo == null || (_swapCreationInfo$fro = swapCreationInfo.fromCoin) == null ? void 0 : _swapCreationInfo$fro.ticker,
|
|
4862
5007
|
toCoin: swapCreationInfo == null || (_swapCreationInfo$toC = swapCreationInfo.toCoin) == null ? void 0 : _swapCreationInfo$toC.ticker
|
|
4863
5008
|
}))}`, loggerSource);
|
|
4864
|
-
const result = await this._swapProvider.createSwap(fromCoin, toCoin, fromAmount, toAddress, refundAddress, swapCreationInfo.rawSwapData, clientIp, toCurrencyExtraId, refundExtraId);
|
|
5009
|
+
const result = await this._swapProvider.createSwap(fromCoin, toCoin, fromAmount, toAddress, refundAddress, swapCreationInfo.rawSwapData, clientIp, swapCreationInfo.fixed, toCurrencyExtraId, refundExtraId);
|
|
4865
5010
|
Logger.log(`Created:${safeStringify(_extends({}, result, {
|
|
4866
5011
|
fromCoin: fromCoin == null ? void 0 : fromCoin.ticker,
|
|
4867
5012
|
toCoin: toCoin == null ? void 0 : toCoin.ticker
|
|
@@ -4899,6 +5044,8 @@ class PublicSwapService {
|
|
|
4899
5044
|
} catch (e) {
|
|
4900
5045
|
Logger.logError(e, loggerSource, "Failed to calculate fiat amounts for result");
|
|
4901
5046
|
}
|
|
5047
|
+
|
|
5048
|
+
// TODO: feature, cirtical] add GA event. task_id=tbd
|
|
4902
5049
|
EventBusInstance.dispatch(PublicSwapService.PUBLIC_SWAP_CREATED_EVENT, null, fromCoin.ticker, toCoin.ticker, fromAmountFiat);
|
|
4903
5050
|
const toReturn = {
|
|
4904
5051
|
result: true,
|
|
@@ -4915,7 +5062,9 @@ class PublicSwapService {
|
|
|
4915
5062
|
durationMinutesRange: swapCreationInfo.durationMinutesRange,
|
|
4916
5063
|
address: result.fromAddress,
|
|
4917
5064
|
// CRITICAL: this is the address to send coins to swaps provider
|
|
4918
|
-
fromCurrencyExtraId: (_result$fromCurrencyE = result.fromCurrencyExtraId) != null ? _result$fromCurrencyE : ""
|
|
5065
|
+
fromCurrencyExtraId: (_result$fromCurrencyE = result.fromCurrencyExtraId) != null ? _result$fromCurrencyE : "",
|
|
5066
|
+
// CRITICAL: this is the extra ID for address to send coins to swaps provider
|
|
5067
|
+
fixed: result.fixed
|
|
4919
5068
|
};
|
|
4920
5069
|
this._savePublicSwapIdLocally(result.swapId);
|
|
4921
5070
|
Logger.log(`Returning: ${safeStringify(_extends({}, toReturn, {
|
|
@@ -5104,9 +5253,11 @@ PublicSwapService.PUBLIC_SWAPS_COMMON_ERRORS = {
|
|
|
5104
5253
|
PublicSwapService.PUBLIC_SWAP_DETAILS_FAIL_REASONS = {
|
|
5105
5254
|
AMOUNT_LESS_THAN_MIN_SWAPPABLE: "amountLessThanMinSwappable",
|
|
5106
5255
|
AMOUNT_HIGHER_THAN_MAX_SWAPPABLE: "amountHigherThanMaxSwappable",
|
|
5107
|
-
PAIR_NOT_SUPPORTED: "pairNotSupported"
|
|
5256
|
+
PAIR_NOT_SUPPORTED: "pairNotSupported",
|
|
5257
|
+
NO_FIXED_BUT_HAVE_FLOATING_PUBLIC_SWAP_OPTION: "noFixedButHaveFloatingPublicSwapOption",
|
|
5258
|
+
NO_FLOATING_BUT_HAVE_FIXED_PUBLIC_SWAP_OPTION: "noFloatingButHaveFixedPublicSwapOption"
|
|
5108
5259
|
};
|
|
5109
5260
|
PublicSwapService._fiatDecimalsCount = FiatCurrenciesService.getCurrencyDecimalCountByCode("USD");
|
|
5110
5261
|
|
|
5111
|
-
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 };
|
|
5262
|
+
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 };
|
|
5112
5263
|
//# sourceMappingURL=index.modern.js.map
|