@rabbitio/ui-kit 1.0.0-beta.4 → 1.0.0-beta.40
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/CHANGELOG.md +0 -0
- package/README.md +23 -16
- package/dist/index.cjs +5336 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +4480 -1635
- package/dist/index.css.map +1 -1
- package/dist/index.modern.js +3766 -11
- package/dist/index.modern.js.map +1 -1
- package/dist/index.module.js +5298 -11
- package/dist/index.module.js.map +1 -1
- package/dist/index.umd.js +5335 -12
- package/dist/index.umd.js.map +1 -1
- package/index.js +1 -1
- package/package.json +16 -22
- package/src/common/adapters/axiosAdapter.js +35 -0
- package/src/common/amountUtils.js +434 -0
- package/src/common/errorUtils.js +42 -0
- package/src/common/external-apis/apiGroups.js +55 -0
- package/src/common/fiatCurrenciesService.js +161 -0
- package/src/common/models/blockchain.js +10 -0
- package/src/common/models/coin.js +157 -0
- package/src/common/models/protocol.js +5 -0
- package/src/common/utils/cache.js +268 -0
- package/src/common/utils/emailAPI.js +18 -0
- package/src/common/utils/logging/logger.js +48 -0
- package/src/common/utils/logging/logsStorage.js +61 -0
- package/src/common/utils/postponeExecution.js +11 -0
- package/src/common/utils/safeStringify.js +50 -0
- package/src/components/atoms/AssetIcon/AssetIcon.jsx +55 -0
- package/src/components/atoms/AssetIcon/asset-icon.module.scss +42 -0
- package/{stories → src/components}/atoms/LoadingDots/LoadingDots.module.scss +1 -1
- package/src/components/atoms/SupportChat/SupportChat.jsx +48 -0
- package/{stories → src/components}/atoms/buttons/Button/Button.jsx +11 -7
- package/{stories → src/components}/atoms/buttons/Button/Button.module.scss +6 -1
- package/src/components/hooks/useCallHandlingErrors.js +26 -0
- package/src/components/hooks/useReferredState.js +24 -0
- package/src/components/utils/uiUtils.js +14 -0
- package/src/components/utils/urlQueryUtils.js +87 -0
- package/src/index.js +52 -0
- package/src/robustExteranlApiCallerService/cacheAndConcurrentRequestsResolver.js +559 -0
- package/src/robustExteranlApiCallerService/cachedRobustExternalApiCallerService.js +188 -0
- package/src/robustExteranlApiCallerService/cancelProcessing.js +29 -0
- package/src/robustExteranlApiCallerService/concurrentCalculationsMetadataHolder.js +103 -0
- package/src/robustExteranlApiCallerService/externalApiProvider.js +156 -0
- package/src/robustExteranlApiCallerService/externalServicesStatsCollector.js +82 -0
- package/src/robustExteranlApiCallerService/robustExternalAPICallerService.js +386 -0
- package/src/swaps-lib/external-apis/swapProvider.js +201 -0
- package/src/swaps-lib/external-apis/swapspaceSwapProvider.js +877 -0
- package/src/swaps-lib/models/baseSwapCreationInfo.js +40 -0
- package/src/swaps-lib/models/existingSwap.js +70 -0
- package/src/swaps-lib/models/existingSwapWithFiatData.js +130 -0
- package/src/swaps-lib/services/publicSwapService.js +674 -0
- package/src/swaps-lib/utils/swapUtils.js +219 -0
- package/stories/index.js +0 -2
- /package/{stories → src/components}/atoms/LoadingDots/LoadingDots.jsx +0 -0
package/dist/index.modern.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
-
import {
|
|
1
|
+
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
|
2
|
+
import { BigNumber } from 'bignumber.js';
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
import { v4 } from 'uuid';
|
|
5
|
+
import Hashes from 'jshashes';
|
|
6
|
+
import EventBusInstance from 'eventbusjs';
|
|
3
7
|
|
|
4
8
|
function createCommonjsModule(fn) {
|
|
5
9
|
var module = { exports: {} };
|
|
@@ -1152,9 +1156,9 @@ if (process.env.NODE_ENV !== 'production') {
|
|
|
1152
1156
|
|
|
1153
1157
|
var PropTypes = propTypes;
|
|
1154
1158
|
|
|
1155
|
-
var styles$1 = {"container":"_Og09u","button":"_Jk9i-","m-0":"_1KRN6","p-0":"_gcKRj","m-1":"_yWCA-","p-1":"_vnN2l","m-2":"_oVgmY","p-2":"_OguP4","m-3":"_Kh1Mk","p-3":"_Z3Isk","m-4":"_6MAp6","p-4":"_Diglh","m-5":"_TO5Nq","p-5":"_HXLSK","m-6":"_HUNIG","p-6":"_f4arD","m-7":"_mZxzR","p-7":"_wzNuy","m-8":"_RVlle","p-8":"_OjqW3","m-9":"_Dejbg","p-9":"_islrX","m-10":"_wj-xZ","p-10":"_xz5kd","ml-0":"_B6vO4","pl-0":"_d137A","ml-1":"_F964v","pl-1":"_cUcnb","ml-2":"_sAUua","pl-2":"_ineea","ml-3":"_Q7vDo","pl-3":"_RaOUn","ml-4":"_QXOxT","pl-4":"_IOfD0","ml-5":"_yxiUW","pl-5":"_Tppy4","ml-6":"_mMW5i","pl-6":"_74GpL","ml-7":"_fFz-u","pl-7":"_QJPKy","ml-8":"_6sPXU","pl-8":"_6-6Yi","ml-9":"_9eU2j","pl-9":"_5QdLq","ml-10":"_AppHk","pl-10":"_czH97","mr-0":"_vcD-O","pr-0":"_yLhIL","mr-1":"_oSLHt","pr-1":"_whfEJ","mr-2":"_nyXH6","pr-2":"_reUdF","mr-3":"_Rs2-c","pr-3":"_sGm42","mr-4":"_vobRL","pr-4":"_wpM-u","mr-5":"_q3dAo","pr-5":"_6PIFR","mr-6":"_ZqDGE","pr-6":"_gw5Dd","mr-7":"_lFAcr","pr-7":"_b36Iz","mr-8":"_PTl3K","pr-8":"_Znc0Y","mr-9":"_P1LS0","pr-9":"_n8V-T","mr-10":"_wdhB1","pr-10":"_xiGuq","mt-0":"_wTXui","pt-0":"_qv-eO","mt-1":"_EZHmH","pt-1":"_rVDqJ","mt-2":"_Vokqr","pt-2":"_Gy6Cr","mt-3":"_W8aoH","pt-3":"_r82YV","mt-4":"_muG0F","pt-4":"_CGaJi","mt-5":"_hiY1E","pt-5":"_7v6rs","mt-6":"_bQZSd","pt-6":"_jAtjI","mt-7":"_R5w8y","pt-7":"_4mWsy","mt-8":"_Sj-di","pt-8":"_-NNao","mt-9":"_Q-fHV","pt-9":"_r8woe","mt-10":"_bSMww","pt-10":"_avmlE","mb-0":"_iCCS3","pb-0":"_gwmOn","mb-1":"_Xl7QX","pb-1":"_WUz0H","mb-2":"_XgCgc","pb-2":"_jPgTM","mb-3":"_3teTS","pb-3":"_dG1rJ","mb-4":"_b9OzE","pb-4":"_IgI2C","mb-5":"_smDd1","pb-5":"_50YOa","mb-6":"_uJfnB","pb-6":"_kl0z7","mb-7":"_L-oZk","pb-7":"_3aui0","mb-8":"_SmtYl","pb-8":"_n-QXT","mb-9":"_mkkKF","pb-9":"_jS1HG","mb-10":"_KDXkK","pb-10":"_eFwJS","mx-0":"_e-HqA","px-0":"_BSmPJ","mx-1":"_x36PW","px-1":"_Gx2xG","mx-2":"_QNnpK","px-2":"_RRwnp","mx-3":"_ZPmuA","px-3":"_f61Ux","mx-4":"_DgyA6","px-4":"_BAKix","mx-5":"_vibgJ","px-5":"_bTxPW","mx-6":"_Yj9Z7","px-6":"_fhT5q","mx-7":"_q-6lp","px-7":"_DQb93","mx-8":"_6G9nX","px-8":"_o-551","mx-9":"_2cubL","px-9":"_4JCU0","mx-10":"_KxEiD","px-10":"_czVGr","my-0":"_CqLSs","py-0":"_8uC-B","my-1":"_ZZG5X","py-1":"_aYbyO","my-2":"_w8TwE","py-2":"_qpOCn","my-3":"_PkA2e","py-3":"_OMDQd","my-4":"_aNFBd","py-4":"_eCXIU","my-5":"_apq2l","py-5":"_mk-sc","my-6":"_sh1-C","py-6":"_ZobGH","my-7":"_8758c","py-7":"_1iT1D","my-8":"_bAfQx","py-8":"_WWkd-","my-9":"_ynwaZ","py-9":"_4bOJI","my-10":"_l-I5b","py-10":"_re6Jp","m-sm-0":"_UPkcx","p-sm-0":"_t5gDA","m-sm-1":"_Rx29j","p-sm-1":"_t6SQY","m-sm-2":"_TyVqN","p-sm-2":"_Z1ogo","m-sm-3":"_gbZcT","p-sm-3":"_Mb1ST","m-sm-4":"_cUvON","p-sm-4":"_Pi4BO","m-sm-5":"_GgM3g","p-sm-5":"_uPFJ2","m-sm-6":"_cCDZS","p-sm-6":"_-e-Jr","m-sm-7":"_f2nRJ","p-sm-7":"_B6vun","m-sm-8":"_pidE0","p-sm-8":"_Qf8oe","m-sm-9":"_OyLmR","p-sm-9":"_soKag","m-sm-10":"_Hnysz","p-sm-10":"_SceBA","ml-sm-0":"_-l5XN","pl-sm-0":"_23QUK","ml-sm-1":"_3nzoa","pl-sm-1":"_Ys-wQ","ml-sm-2":"_cnl0C","pl-sm-2":"_9zniH","ml-sm-3":"_COjSH","pl-sm-3":"_ZoalV","ml-sm-4":"_7PcV0","pl-sm-4":"_yBtfl","ml-sm-5":"_0t-j9","pl-sm-5":"_lrbtT","ml-sm-6":"_441I5","pl-sm-6":"_kDiN3","ml-sm-7":"_7LdG3","pl-sm-7":"_g0gh-","ml-sm-8":"_J0xVz","pl-sm-8":"_Y6n12","ml-sm-9":"_0WKKn","pl-sm-9":"_3Seov","ml-sm-10":"_LbfWV","pl-sm-10":"_yJT8F","mr-sm-0":"_5iTiW","pr-sm-0":"_Z-yPd","mr-sm-1":"_HjxwF","pr-sm-1":"_JibpI","mr-sm-2":"_osijT","pr-sm-2":"_tlR9d","mr-sm-3":"_xxUub","pr-sm-3":"_OM4YU","mr-sm-4":"_tZTrU","pr-sm-4":"_co3SR","mr-sm-5":"_ZV8NG","pr-sm-5":"_6lrgR","mr-sm-6":"_mgH6r","pr-sm-6":"_oirFH","mr-sm-7":"_cDefz","pr-sm-7":"_PbMd1","mr-sm-8":"_GEylU","pr-sm-8":"_w9iBc","mr-sm-9":"_U-I4Y","pr-sm-9":"_VM49L","mr-sm-10":"_A-Fqb","pr-sm-10":"_jAp4X","mt-sm-0":"_kxvbF","pt-sm-0":"_-1AUk","mt-sm-1":"_utpJq","pt-sm-1":"_5aGnP","mt-sm-2":"_hH5bv","pt-sm-2":"_Lf-Hs","mt-sm-3":"_QKQTL","pt-sm-3":"_pS4ep","mt-sm-4":"_cf-lt","pt-sm-4":"_lGSoR","mt-sm-5":"_gSiyG","pt-sm-5":"_WWQxH","mt-sm-6":"_u9dIF","pt-sm-6":"_3rt-l","mt-sm-7":"_9HdeB","pt-sm-7":"_zzUxk","mt-sm-8":"_6dlAC","pt-sm-8":"_gDL-V","mt-sm-9":"_o7STq","pt-sm-9":"_7uyRq","mt-sm-10":"_VRUs5","pt-sm-10":"_nuFOu","mb-sm-0":"_gYIh5","pb-sm-0":"_9EoF6","mb-sm-1":"_h47Qi","pb-sm-1":"_zEE-R","mb-sm-2":"_qNAln","pb-sm-2":"_lAEEB","mb-sm-3":"_qz2f7","pb-sm-3":"_zDV2S","mb-sm-4":"_HziQr","pb-sm-4":"_Z8Nr-","mb-sm-5":"_2-0Mg","pb-sm-5":"_jd6YT","mb-sm-6":"_29u0R","pb-sm-6":"_rv5lz","mb-sm-7":"_34gYX","pb-sm-7":"_QK5rm","mb-sm-8":"_GTTt-","pb-sm-8":"_i9KXS","mb-sm-9":"_aOWnz","pb-sm-9":"_he9qa","mb-sm-10":"_zzLEf","pb-sm-10":"_a3Kx7","mx-sm-0":"_BxA9q","px-sm-0":"_W9ngp","mx-sm-1":"_sw7Ji","px-sm-1":"_CC5F4","mx-sm-2":"_wNsfZ","px-sm-2":"_7HUQW","mx-sm-3":"_7SvR7","px-sm-3":"_MhTrn","mx-sm-4":"_iQFZf","px-sm-4":"_-Kc-C","mx-sm-5":"_uAYfU","px-sm-5":"_zSyRo","mx-sm-6":"_M9NRe","px-sm-6":"_rqh6J","mx-sm-7":"_xC2ZW","px-sm-7":"_2ldRI","mx-sm-8":"_IdyE1","px-sm-8":"_nEOT4","mx-sm-9":"_MZcKX","px-sm-9":"_gn7RL","mx-sm-10":"_qc1L8","px-sm-10":"_eqRhA","my-sm-0":"_Fz6F6","py-sm-0":"_xO1X5","my-sm-1":"_-FpA3","py-sm-1":"_nbaB5","my-sm-2":"_O9I74","py-sm-2":"_luGVj","my-sm-3":"_HiEIn","py-sm-3":"_CYpX7","my-sm-4":"_J6Y94","py-sm-4":"_wB9Cu","my-sm-5":"_NsPY8","py-sm-5":"_mLSOY","my-sm-6":"_KsNN2","py-sm-6":"_ksiG3","my-sm-7":"_I-MIQ","py-sm-7":"_8LmM-","my-sm-8":"_N9K-q","py-sm-8":"_UmDUW","my-sm-9":"_Kx729","py-sm-9":"_T07OJ","my-sm-10":"_IW66T","py-sm-10":"_4gdB-","m-lg-0":"_zIg6p","p-lg-0":"_JuNiH","m-lg-1":"_PYsvw","p-lg-1":"_lcX2G","m-lg-2":"_FqRzp","p-lg-2":"_61LiW","m-lg-3":"_lKWuo","p-lg-3":"_c-9AA","m-lg-4":"_5iPC8","p-lg-4":"_3ucGN","m-lg-5":"_ZcKza","p-lg-5":"_53nUa","m-lg-6":"_nf5Tb","p-lg-6":"_QOkP1","m-lg-7":"_Yaf7S","p-lg-7":"_fS5OB","m-lg-8":"_3hP03","p-lg-8":"_w90dv","m-lg-9":"_eTZmn","p-lg-9":"_rEjn2","m-lg-10":"_MRZfv","p-lg-10":"_GvH8S","ml-lg-0":"_dma-K","pl-lg-0":"_dtgDg","ml-lg-1":"_lSg5Q","pl-lg-1":"_3Bq7W","ml-lg-2":"_ai140","pl-lg-2":"_eHQz2","ml-lg-3":"_IjfYo","pl-lg-3":"_mhnHv","ml-lg-4":"_gB4r4","pl-lg-4":"_GZbML","ml-lg-5":"_i2xfh","pl-lg-5":"_x9yPX","ml-lg-6":"_SeQQZ","pl-lg-6":"_wS39q","ml-lg-7":"_r8f4e","pl-lg-7":"_irihU","ml-lg-8":"_1QsQf","pl-lg-8":"_GXSU9","ml-lg-9":"_MJYz8","pl-lg-9":"_yaJGg","ml-lg-10":"_-AsD8","pl-lg-10":"_yn9Yb","mr-lg-0":"_vjC97","pr-lg-0":"_V3kGD","mr-lg-1":"_wf9z-","pr-lg-1":"_McW7I","mr-lg-2":"_kheh0","pr-lg-2":"_Q9Upz","mr-lg-3":"_zCz0A","pr-lg-3":"_YtfCI","mr-lg-4":"_qQN9B","pr-lg-4":"_S9PbV","mr-lg-5":"_bDTdZ","pr-lg-5":"_AcAC1","mr-lg-6":"_Ei18A","pr-lg-6":"_5YQVo","mr-lg-7":"_x18aw","pr-lg-7":"_N8GDD","mr-lg-8":"_OaFkM","pr-lg-8":"_-9lBB","mr-lg-9":"_52zo-","pr-lg-9":"_9T-Cb","mr-lg-10":"_IXqTb","pr-lg-10":"_zJmVt","mt-lg-0":"_2g62L","pt-lg-0":"_XBXKM","mt-lg-1":"_HMX9J","pt-lg-1":"_7rDIG","mt-lg-2":"_Y6X7U","pt-lg-2":"_fyycG","mt-lg-3":"_DvRKb","pt-lg-3":"_QKOEA","mt-lg-4":"_7qwy-","pt-lg-4":"_wl7f7","mt-lg-5":"_r-Zpt","pt-lg-5":"_-SnjV","mt-lg-6":"_q-hht","pt-lg-6":"_sOK9J","mt-lg-7":"_SUp-d","pt-lg-7":"_PugoZ","mt-lg-8":"_3NhkP","pt-lg-8":"_1n7tp","mt-lg-9":"_N7-FS","pt-lg-9":"_dnC7J","mt-lg-10":"_8IqDn","pt-lg-10":"_zMpbv","mb-lg-0":"_v5nv6","pb-lg-0":"_PMrYJ","mb-lg-1":"_O-soc","pb-lg-1":"_11FYR","mb-lg-2":"_zEeyp","pb-lg-2":"_hXqMT","mb-lg-3":"_san6w","pb-lg-3":"_7Xb0V","mb-lg-4":"_2Vzmv","pb-lg-4":"_fMirW","mb-lg-5":"_pKo-L","pb-lg-5":"_xC4Fx","mb-lg-6":"_snApN","pb-lg-6":"_b9Dsw","mb-lg-7":"_bFu0D","pb-lg-7":"_0qgZX","mb-lg-8":"_Gf3om","pb-lg-8":"_hX-O2","mb-lg-9":"_zZ-9Y","pb-lg-9":"_jk292","mb-lg-10":"_gYyhL","pb-lg-10":"_aPUfC","mx-lg-0":"_tmMx3","px-lg-0":"_GxctH","mx-lg-1":"_Ky0cs","px-lg-1":"_oGNqQ","mx-lg-2":"_9mQLc","px-lg-2":"_laRSV","mx-lg-3":"_xZFwU","px-lg-3":"_BgGbp","mx-lg-4":"_8m58X","px-lg-4":"_hosE8","mx-lg-5":"_r-0ww","px-lg-5":"_pIVfH","mx-lg-6":"_-BStC","px-lg-6":"_v42m-","mx-lg-7":"_DVgVD","px-lg-7":"_olele","mx-lg-8":"_5Vgts","px-lg-8":"_vV7Sg","mx-lg-9":"_R6Pid","px-lg-9":"_qmY7B","mx-lg-10":"_NW4OT","px-lg-10":"_i8Zad","my-lg-0":"_7PFtp","py-lg-0":"_vhTEf","my-lg-1":"_1yOov","py-lg-1":"_oKB3P","my-lg-2":"_loQJ5","py-lg-2":"_S67Pf","my-lg-3":"_HNTDI","py-lg-3":"_LWHvE","my-lg-4":"_b-KHI","py-lg-4":"_0mixG","my-lg-5":"_Vlo-t","py-lg-5":"_xLHlx","my-lg-6":"_0KXBa","py-lg-6":"_9s-me","my-lg-7":"_60cF9","py-lg-7":"_qwIHZ","my-lg-8":"_suf27","py-lg-8":"_c6pD4","my-lg-9":"_KSJpX","py-lg-9":"_C6ZV-","my-lg-10":"_JA-YG","py-lg-10":"_YuxKJ","m-md-0":"_62Zm-","p-md-0":"_7qxYP","m-md-1":"_3xHIn","p-md-1":"_A0jQv","m-md-2":"_gn3k4","p-md-2":"_EpVD0","m-md-3":"_apmcB","p-md-3":"_ldRmv","m-md-4":"_BOjUR","p-md-4":"_93an6","m-md-5":"_W5v56","p-md-5":"_SIBoU","m-md-6":"_VBmMD","p-md-6":"_7ZqTb","m-md-7":"_5Jogx","p-md-7":"_KpbGF","m-md-8":"_lw6bA","p-md-8":"_D21Xs","m-md-9":"_buMId","p-md-9":"_87RlS","m-md-10":"_ojudf","p-md-10":"_sjNOB","ml-md-0":"_V1IX-","pl-md-0":"_ZTBpt","ml-md-1":"_4jtTs","pl-md-1":"_cY-xk","ml-md-2":"_ydZsR","pl-md-2":"_MJk1L","ml-md-3":"_ocW9e","pl-md-3":"_WuBzD","ml-md-4":"_eKU4l","pl-md-4":"_cgptP","ml-md-5":"_Ov-v8","pl-md-5":"_gDnV3","ml-md-6":"_o3eWU","pl-md-6":"_Cr8hz","ml-md-7":"_sX69x","pl-md-7":"_-Ci6A","ml-md-8":"_fGNjF","pl-md-8":"_PeKiX","ml-md-9":"_yVD5i","pl-md-9":"_gxUIi","ml-md-10":"_ZPQrh","pl-md-10":"_oL88W","mr-md-0":"_rETVL","pr-md-0":"_UePrS","mr-md-1":"_wGOhL","pr-md-1":"_lXRvw","mr-md-2":"_5zLmB","pr-md-2":"_gZGtH","mr-md-3":"_jFCGk","pr-md-3":"_iimql","mr-md-4":"_yhePq","pr-md-4":"_xtBfX","mr-md-5":"_KiBHO","pr-md-5":"_WCKkT","mr-md-6":"_Q8jWF","pr-md-6":"_mu6zn","mr-md-7":"_wgy4D","pr-md-7":"_HaN0x","mr-md-8":"_0hc4i","pr-md-8":"_RomM-","mr-md-9":"_vHSBw","pr-md-9":"_4D7JM","mr-md-10":"_4kgs5","pr-md-10":"_KbOqY","mt-md-0":"_PnJEf","pt-md-0":"_Wr2Ts","mt-md-1":"_ZhVjl","pt-md-1":"_0DgRY","mt-md-2":"_WCZNy","pt-md-2":"_632Ah","mt-md-3":"_I1rX1","pt-md-3":"_yLblG","mt-md-4":"_H6sYD","pt-md-4":"_EHXef","mt-md-5":"_A-e3J","pt-md-5":"_-sV7U","mt-md-6":"_hXU9-","pt-md-6":"_n13ly","mt-md-7":"_NvbHC","pt-md-7":"_og4j4","mt-md-8":"_K3M9K","pt-md-8":"_G0D0h","mt-md-9":"_gqgsH","pt-md-9":"_2DuSs","mt-md-10":"_Bd2SX","pt-md-10":"_4zQZt","mb-md-0":"_-zK4R","pb-md-0":"_-5KU3","mb-md-1":"_DsvDi","pb-md-1":"_iBeit","mb-md-2":"_1DyrC","pb-md-2":"_We5TJ","mb-md-3":"_gHMvz","pb-md-3":"_zYpR2","mb-md-4":"_OEEgj","pb-md-4":"_Rc-ZO","mb-md-5":"_qmwpY","pb-md-5":"_t53dC","mb-md-6":"_Z7IKd","pb-md-6":"_Tr1Mj","mb-md-7":"_VAOZd","pb-md-7":"_F6Hvf","mb-md-8":"_bDJti","pb-md-8":"_-Hyix","mb-md-9":"_5NMzS","pb-md-9":"_ka3la","mb-md-10":"_m7Y4u","pb-md-10":"_GKtFW","mx-md-0":"_S-sWu","px-md-0":"_Cbp07","mx-md-1":"_0cWET","px-md-1":"_B62bC","mx-md-2":"_fk8cj","px-md-2":"_hDUD4","mx-md-3":"_3rc6m","px-md-3":"_Ybukf","mx-md-4":"_Cc05e","px-md-4":"_WuZgX","mx-md-5":"_Bizql","px-md-5":"_-p46c","mx-md-6":"_9UcKL","px-md-6":"_JBebf","mx-md-7":"_v-pvB","px-md-7":"_rA-mH","mx-md-8":"_IuwmJ","px-md-8":"_q43Ow","mx-md-9":"_d-qUi","px-md-9":"_z4nCB","mx-md-10":"_kRlX7","px-md-10":"_c-33g","my-md-0":"_PDUR4","py-md-0":"_bYhI-","my-md-1":"_GuZMi","py-md-1":"_odsUL","my-md-2":"_PnosJ","py-md-2":"_eF79-","my-md-3":"_nqJqe","py-md-3":"_Wcv28","my-md-4":"_pg-Cw","py-md-4":"_TQVBc","my-md-5":"_oq3ru","py-md-5":"_ruQ8P","my-md-6":"_udw0b","py-md-6":"_vStkc","my-md-7":"_EHkUk","py-md-7":"_oGosV","my-md-8":"_Sr4G3","py-md-8":"_eDYMt","my-md-9":"_mmQcX","py-md-9":"_J0xZx","my-md-10":"_6PNQm","py-md-10":"_N-J8O","m-xl-0":"_b9ste","p-xl-0":"_09iuo","m-xl-1":"_6sC3f","p-xl-1":"_PtZdh","m-xl-2":"_9tNY-","p-xl-2":"_ovaeX","m-xl-3":"_P2BFM","p-xl-3":"_FEEeY","m-xl-4":"_PVfE9","p-xl-4":"_65pR5","m-xl-5":"_--nhA","p-xl-5":"_jsSAE","m-xl-6":"_R6Xge","p-xl-6":"_m8aQq","m-xl-7":"_siLSo","p-xl-7":"_v7l5L","m-xl-8":"_hHbXe","p-xl-8":"_Rqlns","m-xl-9":"_oGDyI","p-xl-9":"_s-jO5","m-xl-10":"_Acff7","p-xl-10":"_r1WLU","ml-xl-0":"_R-5jP","pl-xl-0":"_4Q5-9","ml-xl-1":"_KtDt9","pl-xl-1":"_WO0X1","ml-xl-2":"_1iTCd","pl-xl-2":"_cwGpB","ml-xl-3":"_U-UBZ","pl-xl-3":"_K6-fp","ml-xl-4":"_Qj-GA","pl-xl-4":"_3HWJh","ml-xl-5":"_tHgFt","pl-xl-5":"_HDbai","ml-xl-6":"_R5iu-","pl-xl-6":"_kiBpR","ml-xl-7":"_IBpxp","pl-xl-7":"_dnEPn","ml-xl-8":"_BKEU-","pl-xl-8":"_Na3WX","ml-xl-9":"_ChhZh","pl-xl-9":"_mn3TM","ml-xl-10":"_-UMhn","pl-xl-10":"_sYOxA","mr-xl-0":"_cpZo1","pr-xl-0":"_JNcnY","mr-xl-1":"_OCoEG","pr-xl-1":"_1muOs","mr-xl-2":"_jhx4t","pr-xl-2":"_vT9VS","mr-xl-3":"_Syl0-","pr-xl-3":"_Vh6yI","mr-xl-4":"_Andea","pr-xl-4":"_IELTR","mr-xl-5":"_tR-Dd","pr-xl-5":"_tFdrE","mr-xl-6":"_MfbBj","pr-xl-6":"_-RDRT","mr-xl-7":"_2TUZ4","pr-xl-7":"_FDwKR","mr-xl-8":"_JILtJ","pr-xl-8":"_vHCTj","mr-xl-9":"_7wU9i","pr-xl-9":"_SqcZw","mr-xl-10":"_V70NV","pr-xl-10":"_-KQQG","mt-xl-0":"_sKVTL","pt-xl-0":"_ZF6Hm","mt-xl-1":"_I5EGT","pt-xl-1":"_-Ig-5","mt-xl-2":"_RIFhP","pt-xl-2":"_zaWNn","mt-xl-3":"_dPd93","pt-xl-3":"_O4wYu","mt-xl-4":"_T1XOW","pt-xl-4":"_b6dFq","mt-xl-5":"_dTI2S","pt-xl-5":"_qyuWu","mt-xl-6":"_0pvAa","pt-xl-6":"_sSQ8V","mt-xl-7":"_P-2W8","pt-xl-7":"_ek09W","mt-xl-8":"_g4vXZ","pt-xl-8":"_b2MMp","mt-xl-9":"_6JmBY","pt-xl-9":"_EG1jC","mt-xl-10":"_tHeBK","pt-xl-10":"_nYubC","mb-xl-0":"_9fyt1","pb-xl-0":"_66Js3","mb-xl-1":"_ARk1H","pb-xl-1":"_kKNTV","mb-xl-2":"_0TmE5","pb-xl-2":"_Zysst","mb-xl-3":"_XiA7F","pb-xl-3":"_SO1hI","mb-xl-4":"_W-4kw","pb-xl-4":"_-sfn8","mb-xl-5":"_NTfVD","pb-xl-5":"_LFC-6","mb-xl-6":"_pp7Gy","pb-xl-6":"_j-vtb","mb-xl-7":"_wpFKE","pb-xl-7":"_BbnyF","mb-xl-8":"_4qmjI","pb-xl-8":"_7pytN","mb-xl-9":"_B3YZo","pb-xl-9":"_cRfJG","mb-xl-10":"_NMhPX","pb-xl-10":"_Ekqdk","mx-xl-0":"_SkbmN","px-xl-0":"_JKZdb","mx-xl-1":"_UKYJi","px-xl-1":"_f-FqJ","mx-xl-2":"_gwtRl","px-xl-2":"_CSJ3f","mx-xl-3":"_tg3Yl","px-xl-3":"_EWUVo","mx-xl-4":"_p2pqT","px-xl-4":"_3DzEy","mx-xl-5":"_0xDrA","px-xl-5":"_NUhX6","mx-xl-6":"_bhY09","px-xl-6":"_mUAfo","mx-xl-7":"_Lug5G","px-xl-7":"_2kUJp","mx-xl-8":"_kTsP1","px-xl-8":"_Chv-7","mx-xl-9":"_h9kIo","px-xl-9":"_gfikM","mx-xl-10":"_3Th0M","px-xl-10":"_A4Mab","my-xl-0":"_62UqO","py-xl-0":"_BkC6O","my-xl-1":"_QJWcf","py-xl-1":"_WWFRs","my-xl-2":"_tkrQM","py-xl-2":"_-qca8","my-xl-3":"_bpoTO","py-xl-3":"_OnKLk","my-xl-4":"_o-kvn","py-xl-4":"_Icmal","my-xl-5":"_cEkTF","py-xl-5":"_mPD3K","my-xl-6":"_b2wEf","py-xl-6":"_SPrOl","my-xl-7":"_BpIhB","py-xl-7":"_9p80f","my-xl-8":"_u3RLs","py-xl-8":"_mc-ae","my-xl-9":"_Q8X-B","py-xl-9":"_SAYlD","my-xl-10":"_vUbME","py-xl-10":"_JgNyS","background-shine":"_31QK4","path":"_LID96","line":"_w2-BZ","skeleton":"_rfGkx","skeleton-animate":"_UY78C","skeleton-dark":"_KmySi","skeleton-dark-animate":"_KF46B","skeleton-transparent":"_50VFe","skeleton-transparent-animate":"_Q6BCG","semi-transparent":"_y91i5","disable":"_87Qkw","full-width-on-tablets":"_wymzG","full-width-on-mobiles":"_uhhSq","xl":"_zdQKm","lg":"_jG9wj","md":"_QrK--","sm":"_jc1W9","small-padding-on-small-mobiles":"_rS2Bk","primary":"_d-gJO","primary-bordered":"_jjfA2","primary-transparent":"_AEbcr","transparent":"_w53db","transparent-bordered":"_WW-Ps","transparent-without-shadow":"_Ltn9c","white":"_csamW","button-primary-dots-wrapper":"_Dmv-l","show":"_GkptB","button-success-icon":"_xImUU","button-text":"_ur-Oh","hide":"_-5XCZ","button-with-icon":"_ZrOKh","button-with-icon-text":"_ixQmJ","big-icon":"_8e4E7"};
|
|
1159
|
+
var styles$1 = {"container":"_lIpAT","button":"_sS-Yj","m-0":"_hBGrg","p-0":"_fOezx","m-1":"_R79vC","p-1":"_BGhZS","m-2":"_4WQa1","p-2":"_-hkj1","m-3":"_ePtXr","p-3":"_ihe2B","m-4":"_KplPo","p-4":"_2vDkp","m-5":"_4AqfI","p-5":"_FfAMs","m-6":"_53MvX","p-6":"_e-x4J","m-7":"_EtdrG","p-7":"_FLCe5","m-8":"_1IHYX","p-8":"_fZzmM","m-9":"_QsD9D","p-9":"_6Sr2Q","m-10":"_LvJdU","p-10":"_ah-tz","ml-0":"_De6uE","pl-0":"_aQ1ph","ml-1":"_5Bwy4","pl-1":"_j4wAR","ml-2":"_qeK50","pl-2":"_VC868","ml-3":"_LuNZb","pl-3":"_3fxbv","ml-4":"_XiU0I","pl-4":"_hPWPx","ml-5":"_xkmy7","pl-5":"_vZWUx","ml-6":"_HJ-Oe","pl-6":"_nx5bN","ml-7":"_L-cTm","pl-7":"_XyNJZ","ml-8":"_GUJTt","pl-8":"_O-OPx","ml-9":"_4DBUs","pl-9":"_W5E4J","ml-10":"_TsFNa","pl-10":"_a07G-","mr-0":"_KuY5-","pr-0":"_Zv2gF","mr-1":"_IylJL","pr-1":"_DIaMC","mr-2":"_wodzN","pr-2":"_x7vGR","mr-3":"_cg-WA","pr-3":"_-GR0e","mr-4":"_zip09","pr-4":"_mrzBz","mr-5":"_Z3SdR","pr-5":"_FOt7u","mr-6":"_DX3gj","pr-6":"_caE8K","mr-7":"_SRVDr","pr-7":"_7h7Xw","mr-8":"_bvjcP","pr-8":"_WxJl8","mr-9":"_CDXo1","pr-9":"_uftSg","mr-10":"_WqvBW","pr-10":"_75aX1","mt-0":"_lfLEb","pt-0":"_FszNW","mt-1":"_xtok6","pt-1":"_cDFtN","mt-2":"_j96uZ","pt-2":"_BX40s","mt-3":"_3--yt","pt-3":"_qkmXr","mt-4":"_DhLUo","pt-4":"_AG7V9","mt-5":"_Gn5bX","pt-5":"_5-rlk","mt-6":"_c6g6t","pt-6":"_iWQbs","mt-7":"_6X-oB","pt-7":"_i82Td","mt-8":"_uK93t","pt-8":"_ESUrY","mt-9":"_LmSrq","pt-9":"_BgZxc","mt-10":"_mPy8r","pt-10":"_ddKoS","mb-0":"_gs1hc","pb-0":"_k94Xn","mb-1":"_8XN6g","pb-1":"_lJSHs","mb-2":"_y04oG","pb-2":"_cNkcX","mb-3":"_3-Ex4","pb-3":"_UlTDL","mb-4":"_ZchCM","pb-4":"_PrDJ0","mb-5":"_Al-qI","pb-5":"_9ExxH","mb-6":"_38fJH","pb-6":"_YPJnw","mb-7":"_ViLUm","pb-7":"_YtQHK","mb-8":"_Jm2m3","pb-8":"_hQSln","mb-9":"_73cuP","pb-9":"_Hm-oe","mb-10":"_90atw","pb-10":"_GxdOW","mx-0":"_786dB","px-0":"_b-KUL","mx-1":"_1sw2D","px-1":"_pfikG","mx-2":"_ZCsfc","px-2":"_-E-rP","mx-3":"_Vzqfk","px-3":"_Evygh","mx-4":"_g6XKX","px-4":"_hzPZK","mx-5":"_6Yusw","px-5":"_43D2o","mx-6":"_l7jig","px-6":"_X4DAB","mx-7":"_xCEJ4","px-7":"_kFsKL","mx-8":"_bcZ3x","px-8":"_qKJ-o","mx-9":"_pmzk-","px-9":"_5kFZh","mx-10":"_ZtyHL","px-10":"_RzSP4","my-0":"_iGkHt","py-0":"_tFXK8","my-1":"_9p151","py-1":"_1ExYH","my-2":"_hDX-y","py-2":"_j2Iqz","my-3":"_ZoP-i","py-3":"_JiyfX","my-4":"_6dHeQ","py-4":"_Val1l","my-5":"_yF8w9","py-5":"_3Do00","my-6":"_5ATW7","py-6":"_EqQi0","my-7":"_6Kng-","py-7":"_kFB05","my-8":"_fqkrf","py-8":"_wkYlc","my-9":"_yC6ha","py-9":"_nR4hK","my-10":"_46v2E","py-10":"_yjbuZ","m-sm-0":"_mXg7O","p-sm-0":"_sRkzF","m-sm-1":"_uh76t","p-sm-1":"_mnvOq","m-sm-2":"_-Lxwk","p-sm-2":"_9AoV6","m-sm-3":"_k6uGI","p-sm-3":"_FPlck","m-sm-4":"_XWNGm","p-sm-4":"_UhY4z","m-sm-5":"_nqmEm","p-sm-5":"_O3yzT","m-sm-6":"_1C7QT","p-sm-6":"_yR3CC","m-sm-7":"_vlqMl","p-sm-7":"_otIfI","m-sm-8":"_f-GUL","p-sm-8":"_oMnLQ","m-sm-9":"_Klhy3","p-sm-9":"_xYdqe","m-sm-10":"_jg7tY","p-sm-10":"_-28KU","ml-sm-0":"_6hhVb","pl-sm-0":"_WV4uZ","ml-sm-1":"_u--dD","pl-sm-1":"_k2dSZ","ml-sm-2":"_ng1Wx","pl-sm-2":"_A6pLt","ml-sm-3":"_OKwcI","pl-sm-3":"_uxcE6","ml-sm-4":"_dHy5x","pl-sm-4":"_loKKN","ml-sm-5":"_pWXnn","pl-sm-5":"_6KcZ0","ml-sm-6":"_8DdT4","pl-sm-6":"_atdfH","ml-sm-7":"_-BksR","pl-sm-7":"_B6uGl","ml-sm-8":"_rL6Yi","pl-sm-8":"_L0Gt6","ml-sm-9":"_sDCuc","pl-sm-9":"_5ndTT","ml-sm-10":"_d0sOv","pl-sm-10":"_MzCci","mr-sm-0":"_TxpxL","pr-sm-0":"_ReTjB","mr-sm-1":"_ZDd-C","pr-sm-1":"_6ux6P","mr-sm-2":"_SbIys","pr-sm-2":"_0dAjY","mr-sm-3":"_WT-FE","pr-sm-3":"_hgzpB","mr-sm-4":"_ovLOk","pr-sm-4":"_YVXRD","mr-sm-5":"_3gKfj","pr-sm-5":"_GKqKE","mr-sm-6":"_EJ3Ig","pr-sm-6":"_pKU7W","mr-sm-7":"_MfWyc","pr-sm-7":"_439vk","mr-sm-8":"_HsLqa","pr-sm-8":"_Afu8-","mr-sm-9":"_k-uXQ","pr-sm-9":"_GSZg6","mr-sm-10":"_buR-E","pr-sm-10":"_GoGHq","mt-sm-0":"_3eC5V","pt-sm-0":"_w0Row","mt-sm-1":"_lFsar","pt-sm-1":"_rhMZX","mt-sm-2":"_HchNc","pt-sm-2":"_Xyn5a","mt-sm-3":"_INU0e","pt-sm-3":"_i9zkC","mt-sm-4":"_d1xqQ","pt-sm-4":"_eMU-M","mt-sm-5":"_gYMLN","pt-sm-5":"_sG2g-","mt-sm-6":"_U4BDm","pt-sm-6":"_nagya","mt-sm-7":"_f4nLP","pt-sm-7":"_3jMHw","mt-sm-8":"_hZW3J","pt-sm-8":"_woC7I","mt-sm-9":"_SFf2s","pt-sm-9":"_nuLoR","mt-sm-10":"_k1lF9","pt-sm-10":"_WG3-N","mb-sm-0":"_fQPR-","pb-sm-0":"_eL6LV","mb-sm-1":"_Z7gTR","pb-sm-1":"_OdGhD","mb-sm-2":"_dkjwo","pb-sm-2":"_kKhvE","mb-sm-3":"_meX0E","pb-sm-3":"_kh7Ku","mb-sm-4":"_rrb92","pb-sm-4":"_aytJc","mb-sm-5":"_wPkH4","pb-sm-5":"_1nZ-9","mb-sm-6":"_MkdDA","pb-sm-6":"_BxNLc","mb-sm-7":"_Nfm1A","pb-sm-7":"_-r452","mb-sm-8":"_K1omF","pb-sm-8":"_4-6gt","mb-sm-9":"_OUWsR","pb-sm-9":"_7DXr5","mb-sm-10":"_wZv7f","pb-sm-10":"_21WIP","mx-sm-0":"_hCRDg","px-sm-0":"_Bdj31","mx-sm-1":"_sx6bH","px-sm-1":"_jQXbZ","mx-sm-2":"_aCjIb","px-sm-2":"_-DBsZ","mx-sm-3":"_p2bFN","px-sm-3":"_d6ice","mx-sm-4":"_GZmNE","px-sm-4":"_7D10W","mx-sm-5":"_tKkEN","px-sm-5":"_J2iH5","mx-sm-6":"_iJju-","px-sm-6":"_m2ygK","mx-sm-7":"_chPCu","px-sm-7":"_C0aVt","mx-sm-8":"_twRB-","px-sm-8":"_6DezI","mx-sm-9":"_z1iZL","px-sm-9":"_HLE-F","mx-sm-10":"_F58U0","px-sm-10":"_Mat2S","my-sm-0":"_nADuT","py-sm-0":"_yu79u","my-sm-1":"_TEHyf","py-sm-1":"_MkRV5","my-sm-2":"_wfxJN","py-sm-2":"_qjIa5","my-sm-3":"_ch7TX","py-sm-3":"_0ZM5Q","my-sm-4":"_KQyj3","py-sm-4":"_npV56","my-sm-5":"_antvz","py-sm-5":"_qK29t","my-sm-6":"_PIZw6","py-sm-6":"_kwzra","my-sm-7":"_IPjVc","py-sm-7":"_NPJ-2","my-sm-8":"_Sxu2p","py-sm-8":"_VUG-4","my-sm-9":"_mLy-T","py-sm-9":"_U-1Tz","my-sm-10":"_6LGF-","py-sm-10":"_1I7Qo","m-lg-0":"_7JHxT","p-lg-0":"_dZxSG","m-lg-1":"_ap69K","p-lg-1":"_yAiEU","m-lg-2":"_cu0Gq","p-lg-2":"_p2Nvp","m-lg-3":"_ipTnO","p-lg-3":"_uekmi","m-lg-4":"_Ocj1U","p-lg-4":"_T5xZ0","m-lg-5":"_9QwAr","p-lg-5":"_S73RR","m-lg-6":"_TQDUS","p-lg-6":"_SVpzt","m-lg-7":"_u7-7x","p-lg-7":"_tVwwR","m-lg-8":"_VMheR","p-lg-8":"_dnoel","m-lg-9":"_bs68b","p-lg-9":"_-fPka","m-lg-10":"_JaRq8","p-lg-10":"_222SK","ml-lg-0":"_snW5C","pl-lg-0":"_cbC4p","ml-lg-1":"_ZcZqo","pl-lg-1":"_Ox1R-","ml-lg-2":"_GYhhL","pl-lg-2":"_RQnGa","ml-lg-3":"_PUix3","pl-lg-3":"_TXjNe","ml-lg-4":"_ctO4e","pl-lg-4":"_HGdnK","ml-lg-5":"_0kAql","pl-lg-5":"_j374-","ml-lg-6":"_DTSzU","pl-lg-6":"_f8fRt","ml-lg-7":"_SOGpN","pl-lg-7":"_ftCov","ml-lg-8":"_yyg3O","pl-lg-8":"_es-pp","ml-lg-9":"_ujiYm","pl-lg-9":"_hEH2f","ml-lg-10":"_sOymp","pl-lg-10":"_zABMD","mr-lg-0":"_bZvOK","pr-lg-0":"_1MjdX","mr-lg-1":"_lVGe-","pr-lg-1":"_hJzV5","mr-lg-2":"_9az71","pr-lg-2":"_vBVKt","mr-lg-3":"_SK-xp","pr-lg-3":"_BymTx","mr-lg-4":"_C-P-K","pr-lg-4":"_oDktY","mr-lg-5":"_e8X2c","pr-lg-5":"_XugCG","mr-lg-6":"_CycFS","pr-lg-6":"_aXn7x","mr-lg-7":"_woi9n","pr-lg-7":"_Xz9XT","mr-lg-8":"_p4H3g","pr-lg-8":"_2GHoq","mr-lg-9":"_OZukC","pr-lg-9":"_ckY7k","mr-lg-10":"_tu3W5","pr-lg-10":"_9v3FG","mt-lg-0":"_I-1g6","pt-lg-0":"_cUyFD","mt-lg-1":"_c94QP","pt-lg-1":"_g75ry","mt-lg-2":"_XiT9Y","pt-lg-2":"_OzwIr","mt-lg-3":"_TZnYh","pt-lg-3":"_qC-3V","mt-lg-4":"_ClD8h","pt-lg-4":"_n1T93","mt-lg-5":"_yfqL8","pt-lg-5":"_oRkf6","mt-lg-6":"_6sndc","pt-lg-6":"_zKuFI","mt-lg-7":"_61kz3","pt-lg-7":"_5Q2hr","mt-lg-8":"_s2ReT","pt-lg-8":"_Ap8SS","mt-lg-9":"_bV6Hp","pt-lg-9":"_G5Dl9","mt-lg-10":"_hSJ91","pt-lg-10":"_amgjj","mb-lg-0":"_p-7me","pb-lg-0":"_ys8Fm","mb-lg-1":"_ZFPzR","pb-lg-1":"_Low3Q","mb-lg-2":"_UKfD5","pb-lg-2":"_Z4xYz","mb-lg-3":"_WYCZq","pb-lg-3":"_lOmpq","mb-lg-4":"_3f13Q","pb-lg-4":"_tfvEW","mb-lg-5":"_DxAtv","pb-lg-5":"_lN1KV","mb-lg-6":"_pEAAo","pb-lg-6":"_9pLfP","mb-lg-7":"_lZzWZ","pb-lg-7":"_E76Lg","mb-lg-8":"_Biywb","pb-lg-8":"_PKNGt","mb-lg-9":"_af7LP","pb-lg-9":"_769vF","mb-lg-10":"_p6W2n","pb-lg-10":"_44r6t","mx-lg-0":"_noonL","px-lg-0":"_g6n-m","mx-lg-1":"_V3fJC","px-lg-1":"_h-048","mx-lg-2":"_VBXFM","px-lg-2":"_yXjeI","mx-lg-3":"_anxPB","px-lg-3":"_umXxh","mx-lg-4":"_arYWt","px-lg-4":"_v21qY","mx-lg-5":"_VJrBv","px-lg-5":"_Wg7-s","mx-lg-6":"_qiDC7","px-lg-6":"_1VXpg","mx-lg-7":"_d0c15","px-lg-7":"_sfSeF","mx-lg-8":"_2gYLA","px-lg-8":"_DEL7d","mx-lg-9":"_KmU1j","px-lg-9":"_Qs2ud","mx-lg-10":"_cEBsK","px-lg-10":"_mQH7b","my-lg-0":"_ag2WO","py-lg-0":"_lnVBo","my-lg-1":"_xEHQX","py-lg-1":"_9Tj2M","my-lg-2":"_Iqbzp","py-lg-2":"_8tcVx","my-lg-3":"_caNHW","py-lg-3":"_XJ-2h","my-lg-4":"_6q91a","py-lg-4":"_EwkV-","my-lg-5":"_vAyw8","py-lg-5":"_5fbq2","my-lg-6":"_15LIk","py-lg-6":"_HIyvD","my-lg-7":"_kBk-L","py-lg-7":"_t5Eqq","my-lg-8":"_7QdOv","py-lg-8":"_mWXQT","my-lg-9":"_AgF1j","py-lg-9":"_Hw03a","my-lg-10":"_lgtEt","py-lg-10":"_VLUO-","m-md-0":"_bZzK1","p-md-0":"_hf1u1","m-md-1":"_D90hf","p-md-1":"_T8Wci","m-md-2":"_OV2C9","p-md-2":"_GFg-F","m-md-3":"_tOGww","p-md-3":"_p0NdJ","m-md-4":"_gUtm-","p-md-4":"_92JAP","m-md-5":"_V4DwP","p-md-5":"_KzYIR","m-md-6":"_RYzMI","p-md-6":"_bV-UI","m-md-7":"_bZ8rT","p-md-7":"_sz5lw","m-md-8":"_Tt85A","p-md-8":"_o9XsL","m-md-9":"_5Z2zG","p-md-9":"_QFUvw","m-md-10":"_o-CSv","p-md-10":"_I0UW7","ml-md-0":"_Ec-jt","pl-md-0":"_AsNUW","ml-md-1":"_FkvyK","pl-md-1":"_eWpHd","ml-md-2":"_C-N9j","pl-md-2":"_f4IXC","ml-md-3":"_i4rpE","pl-md-3":"_u1wxV","ml-md-4":"_bfpFB","pl-md-4":"_LFkDV","ml-md-5":"_ChenT","pl-md-5":"_Tseed","ml-md-6":"_5OVzE","pl-md-6":"_kKZuw","ml-md-7":"_HkEvd","pl-md-7":"_-UCQ1","ml-md-8":"_Mil4J","pl-md-8":"_ibj8N","ml-md-9":"_hZgvD","pl-md-9":"_x58o7","ml-md-10":"_aNTRb","pl-md-10":"_B9Ytk","mr-md-0":"_5fzNR","pr-md-0":"_UcmnK","mr-md-1":"_7GSVQ","pr-md-1":"_lKZ6y","mr-md-2":"_Vuc2l","pr-md-2":"_OWoeF","mr-md-3":"_GfGNW","pr-md-3":"_039xq","mr-md-4":"_XUUw7","pr-md-4":"_hNdK8","mr-md-5":"_PchvO","pr-md-5":"_YBTBv","mr-md-6":"_irZPz","pr-md-6":"_xez9v","mr-md-7":"_IWCJD","pr-md-7":"_Nymwz","mr-md-8":"_1ypEg","pr-md-8":"_hJvwk","mr-md-9":"_AisV-","pr-md-9":"_6jqua","mr-md-10":"_LdBUv","pr-md-10":"_Rhahl","mt-md-0":"_iI75A","pt-md-0":"_GCK2h","mt-md-1":"_QoRcE","pt-md-1":"_Q3gZh","mt-md-2":"_rzNrf","pt-md-2":"_mrtOR","mt-md-3":"_-Xx-x","pt-md-3":"_vhZY4","mt-md-4":"_Ie5M9","pt-md-4":"_Bv-Vz","mt-md-5":"_sC4OE","pt-md-5":"_sZzfz","mt-md-6":"_b8qW3","pt-md-6":"_sca5l","mt-md-7":"_Pn6m9","pt-md-7":"_9klOR","mt-md-8":"_oY4i-","pt-md-8":"_QpcKU","mt-md-9":"_-PssV","pt-md-9":"_IBITO","mt-md-10":"_hmDGa","pt-md-10":"_rPOnf","mb-md-0":"_KsSuI","pb-md-0":"_E01ZW","mb-md-1":"_mu8n4","pb-md-1":"_obt8N","mb-md-2":"_4uUmS","pb-md-2":"_eCQut","mb-md-3":"_gckFx","pb-md-3":"_v76WA","mb-md-4":"_HNG1G","pb-md-4":"_WF2cS","mb-md-5":"_y0d7f","pb-md-5":"_Ulg5o","mb-md-6":"_gGNca","pb-md-6":"_L6tTJ","mb-md-7":"_JFKoe","pb-md-7":"_cotbI","mb-md-8":"_zWuLC","pb-md-8":"_PZ98d","mb-md-9":"_0OLPw","pb-md-9":"_diSZ0","mb-md-10":"_tZv9r","pb-md-10":"_8DINx","mx-md-0":"_TlILw","px-md-0":"_nYb1A","mx-md-1":"_8F9Eu","px-md-1":"_zdtgY","mx-md-2":"_Hf2VU","px-md-2":"_R7ntT","mx-md-3":"_7koGZ","px-md-3":"_SjYZ0","mx-md-4":"_mfX1U","px-md-4":"_wpmO4","mx-md-5":"_yWbVN","px-md-5":"_c-PDJ","mx-md-6":"_F98A-","px-md-6":"_k2aB-","mx-md-7":"_PyjZi","px-md-7":"_1p7LO","mx-md-8":"_at5B-","px-md-8":"_Wlop7","mx-md-9":"_MP-IT","px-md-9":"_ZEJLn","mx-md-10":"_t-qxH","px-md-10":"_m2jcH","my-md-0":"_mamXD","py-md-0":"_XfnLL","my-md-1":"_wltQA","py-md-1":"_kMSov","my-md-2":"_PB9Lu","py-md-2":"_ZHQxJ","my-md-3":"_ROfrO","py-md-3":"_5pjW-","my-md-4":"_asOWk","py-md-4":"_521lJ","my-md-5":"_sfKXT","py-md-5":"_0hhLg","my-md-6":"_vBIkT","py-md-6":"_mh-mU","my-md-7":"_SbaIW","py-md-7":"_4U--m","my-md-8":"_8Ml4g","py-md-8":"_8CAbC","my-md-9":"_2W5Vi","py-md-9":"_ifVgs","my-md-10":"_jOwwL","py-md-10":"_xGEHg","m-xl-0":"_Cmi8h","p-xl-0":"_YOJnv","m-xl-1":"_IlYRc","p-xl-1":"_qg39r","m-xl-2":"_NZu1F","p-xl-2":"_ChCeZ","m-xl-3":"_wCJFO","p-xl-3":"_s5DXB","m-xl-4":"_-ooyS","p-xl-4":"_1rcFF","m-xl-5":"_2--cB","p-xl-5":"_0s8cD","m-xl-6":"_zMbs-","p-xl-6":"_qAR79","m-xl-7":"_sRWyz","p-xl-7":"_ykFCJ","m-xl-8":"_dj226","p-xl-8":"_XLB7-","m-xl-9":"_eM-R4","p-xl-9":"_QuQVl","m-xl-10":"_UUuwy","p-xl-10":"_nDqX9","ml-xl-0":"_LL8WU","pl-xl-0":"_AIPph","ml-xl-1":"_pKFo-","pl-xl-1":"_tQEXN","ml-xl-2":"_F-wn7","pl-xl-2":"_wcxv3","ml-xl-3":"_M94zg","pl-xl-3":"_I3KoK","ml-xl-4":"_0PzeG","pl-xl-4":"_lzci3","ml-xl-5":"_XRI4C","pl-xl-5":"_XlhyA","ml-xl-6":"_GjzL0","pl-xl-6":"_ASrOm","ml-xl-7":"_-0WjA","pl-xl-7":"_hx5P8","ml-xl-8":"_XlbUH","pl-xl-8":"_Vonli","ml-xl-9":"_LwuSL","pl-xl-9":"_MxuFJ","ml-xl-10":"_v7xZI","pl-xl-10":"_Tvo-Z","mr-xl-0":"_SzVWx","pr-xl-0":"_K1dhq","mr-xl-1":"_JOZtq","pr-xl-1":"_c1i37","mr-xl-2":"_R3B8Q","pr-xl-2":"_63cME","mr-xl-3":"_91LCJ","pr-xl-3":"_WxNaS","mr-xl-4":"_BauAg","pr-xl-4":"_V6rST","mr-xl-5":"_H0Uy4","pr-xl-5":"_idyBA","mr-xl-6":"_lIBS-","pr-xl-6":"_hm7f2","mr-xl-7":"_DcV1a","pr-xl-7":"_L9DAM","mr-xl-8":"_M62Cx","pr-xl-8":"_nso5O","mr-xl-9":"_M-vkg","pr-xl-9":"_Jm-de","mr-xl-10":"_V5v9Y","pr-xl-10":"_jt-a5","mt-xl-0":"_YPHmA","pt-xl-0":"_XVXEh","mt-xl-1":"_pJbYc","pt-xl-1":"_i-ZXc","mt-xl-2":"_SHsME","pt-xl-2":"_ak-Hh","mt-xl-3":"_wzbs7","pt-xl-3":"_py3lR","mt-xl-4":"_HBXKA","pt-xl-4":"_GQBlv","mt-xl-5":"_5d6Ee","pt-xl-5":"_KFPkO","mt-xl-6":"_Yf-yF","pt-xl-6":"_1e6oC","mt-xl-7":"_kg9lU","pt-xl-7":"_SHs7f","mt-xl-8":"_KXugL","pt-xl-8":"_VyRfA","mt-xl-9":"_EOyB7","pt-xl-9":"_5KMcS","mt-xl-10":"_8W-Zj","pt-xl-10":"_cz8iz","mb-xl-0":"_rWALs","pb-xl-0":"_LpkfT","mb-xl-1":"_RzmHQ","pb-xl-1":"_6ad54","mb-xl-2":"_Ajc4e","pb-xl-2":"_SbX8t","mb-xl-3":"_vSzzB","pb-xl-3":"_yidEJ","mb-xl-4":"_mrCP3","pb-xl-4":"_3TrW-","mb-xl-5":"_6j8-6","pb-xl-5":"_D1WrN","mb-xl-6":"_XDRU9","pb-xl-6":"_dcA8T","mb-xl-7":"_-i7kC","pb-xl-7":"_81QE4","mb-xl-8":"_X9-zp","pb-xl-8":"_HTClM","mb-xl-9":"_klb-D","pb-xl-9":"_RReN9","mb-xl-10":"_vPsb7","pb-xl-10":"_OVfeI","mx-xl-0":"_YNCB1","px-xl-0":"_hiVA4","mx-xl-1":"_k6kz-","px-xl-1":"_rONSC","mx-xl-2":"_pBneY","px-xl-2":"_po-3F","mx-xl-3":"_Bpbr7","px-xl-3":"_KdGyy","mx-xl-4":"_brh6E","px-xl-4":"_qqxT0","mx-xl-5":"_BfzzU","px-xl-5":"_R-Rw5","mx-xl-6":"_sSR5n","px-xl-6":"_thByS","mx-xl-7":"_r5nPW","px-xl-7":"_Qn6um","mx-xl-8":"_J9EDO","px-xl-8":"_9rkVI","mx-xl-9":"_Mltks","px-xl-9":"_g-Ham","mx-xl-10":"_sbya6","px-xl-10":"_8-jLj","my-xl-0":"_5yWBG","py-xl-0":"_xbHHG","my-xl-1":"_PlYBL","py-xl-1":"_F3Fon","my-xl-2":"_tsmUe","py-xl-2":"_a-0sQ","my-xl-3":"_sY3wJ","py-xl-3":"_iMcG-","my-xl-4":"_oHAzn","py-xl-4":"_SVfuw","my-xl-5":"_PvaIf","py-xl-5":"_J1xNt","my-xl-6":"_opQCd","py-xl-6":"_BykWn","my-xl-7":"_wYtgV","py-xl-7":"_rWN6g","my-xl-8":"_QHTpr","py-xl-8":"_6lwHD","my-xl-9":"_sKUSa","py-xl-9":"_HEg44","my-xl-10":"_mR1Ig","py-xl-10":"_-KK9e","background-shine":"_GS93D","path":"_Yio2J","line":"_hUBrW","skeleton":"_jhzCK","skeleton-animate":"_E3Vdv","skeleton-dark":"_wQ36Y","skeleton-dark-animate":"_j-rmG","skeleton-transparent":"_trTiX","skeleton-transparent-animate":"_4QQUW","semi-transparent":"_jW9cZ","disable":"_07-2N","full-width-on-tablets":"_at3Is","full-width-on-mobiles":"_7mVhH","xl":"_YBEjy","lg":"_4T1JY","md":"_y81Q7","sm":"_HlFvs","small-padding-on-small-mobiles":"_pC5i6","primary":"_rf33o","primary-bordered":"_bpdoC","primary-transparent":"_FNwOe","transparent":"_9LspZ","transparent-bordered":"_HgM7r","transparent-without-shadow":"_tkp-m","white":"_bv597","white-flat":"_IhewT","button-primary-dots-wrapper":"_OEKG8","show":"_MQYPo","button-success-icon":"_Y24W2","button-text":"_o-rLq","hide":"_4GC9-","button-with-icon":"_m1W6m","button-with-icon-text":"_75-fw","big-icon":"_gHcQi"};
|
|
1156
1160
|
|
|
1157
|
-
var styles = {"container":"_wfTsj","m-0":"_UeiuB","p-0":"_QD4AZ","m-1":"_wOM1E","p-1":"_kJ3GD","m-2":"_5G5Rf","p-2":"_dyuup","m-3":"_m4kZr","p-3":"_-aSKV","m-4":"_lqeth","p-4":"_WFhaj","m-5":"_bgee8","p-5":"_Y2Iup","m-6":"_c873k","p-6":"_Huak-","m-7":"_c2rt4","p-7":"_2dT3Y","m-8":"_7rQUz","p-8":"_fjzT8","m-9":"_jmGJQ","p-9":"_CTpOZ","m-10":"_lEJ9M","p-10":"_1LfZ2","ml-0":"_j6sNe","pl-0":"_i8R7s","ml-1":"_oM6Eu","pl-1":"_QllX8","ml-2":"_r3xcV","pl-2":"_fd0jL","ml-3":"_SP5MI","pl-3":"_wQD-b","ml-4":"_uV9BF","pl-4":"_k1Kla","ml-5":"_FTlGy","pl-5":"_k20cZ","ml-6":"_5gR0D","pl-6":"_ICAcq","ml-7":"_-PQGU","pl-7":"_VxSDF","ml-8":"_bKyKZ","pl-8":"_2DqEG","ml-9":"_1SlOv","pl-9":"_EIiNs","ml-10":"_5WCGB","pl-10":"_qdKd3","mr-0":"_JWuXP","pr-0":"_5aqFq","mr-1":"_GTimu","pr-1":"_VC6Mm","mr-2":"_iuK1E","pr-2":"_YREDe","mr-3":"_qttq2","pr-3":"_FRB7c","mr-4":"_tyLXy","pr-4":"_khAbL","mr-5":"_-pPmp","pr-5":"_Zn6P3","mr-6":"_UXDvD","pr-6":"_Pyjz-","mr-7":"_ydv6O","pr-7":"_W2-PF","mr-8":"_AeBpV","pr-8":"_GNhM6","mr-9":"_6r58X","pr-9":"_nDQS4","mr-10":"_z6oMu","pr-10":"_NqF8M","mt-0":"_5BywF","pt-0":"_G3lb7","mt-1":"_O8qGt","pt-1":"_KxUDK","mt-2":"_feZvx","pt-2":"_i4LGd","mt-3":"_8rWTr","pt-3":"_5KtzE","mt-4":"_22EES","pt-4":"_ktDxC","mt-5":"_hNJ8a","pt-5":"_l-BdA","mt-6":"_V7rTE","pt-6":"_yO380","mt-7":"_64Gbc","pt-7":"_slV9V","mt-8":"_PAPZ0","pt-8":"_SlH81","mt-9":"_OmKU7","pt-9":"_m4Y5T","mt-10":"_N7MOD","pt-10":"_5EmMY","mb-0":"_umP3K","pb-0":"_tdjdM","mb-1":"_RUpVI","pb-1":"_qhSG-","mb-2":"_UE6hY","pb-2":"_bhwY3","mb-3":"_gCNY5","pb-3":"_CT9SA","mb-4":"_hXf14","pb-4":"_dBd4j","mb-5":"_BJm2f","pb-5":"_osAXf","mb-6":"_sksVG","pb-6":"_vq2P1","mb-7":"_GGqWv","pb-7":"_Wp-xn","mb-8":"_ao8-b","pb-8":"_Fcz6J","mb-9":"_HTDCN","pb-9":"_j2geM","mb-10":"_tQ3f4","pb-10":"_k5-cY","mx-0":"_C-RwH","px-0":"_fGmBQ","mx-1":"_SJIAI","px-1":"_V6h6n","mx-2":"_RpAL-","px-2":"_MtnJr","mx-3":"_bJAjq","loading-dots":"_i9YR0","big":"_Gf-5X","dot":"_yU4CC","px-3":"_bKF6h","mx-4":"_2YwpF","px-4":"_sv9HO","mx-5":"_fyywB","px-5":"_PAvUg","mx-6":"_-7Y93","px-6":"_2HnrR","mx-7":"_mO0GJ","px-7":"_-XBUb","mx-8":"_rPzNl","px-8":"_lxUkB","mx-9":"_wtwwN","px-9":"_ewGa-","mx-10":"_CUJzg","px-10":"_9fyow","my-0":"_D3hCP","py-0":"_fUhkb","my-1":"_-gS0u","py-1":"_DCjg-","my-2":"_REwX3","py-2":"_5qEqQ","my-3":"_4P9yd","py-3":"_3Q6jo","my-4":"_pJ9JL","py-4":"_fvyEP","my-5":"_7HH0Y","py-5":"_pfPnT","my-6":"_HMzw4","py-6":"_nGGuj","my-7":"_ZQcHb","py-7":"_Q5rCt","my-8":"_yurdg","py-8":"_z2Rnm","my-9":"_SGsqH","py-9":"_RkTBo","my-10":"_hgG--","py-10":"_iKSyb","m-sm-0":"_hG8Pd","p-sm-0":"_paicL","m-sm-1":"_zYVem","p-sm-1":"_Iifls","m-sm-2":"_C0HAk","p-sm-2":"_tyMH8","m-sm-3":"_Nw8NT","p-sm-3":"_mOvjR","m-sm-4":"_auqaY","p-sm-4":"_nqq-T","m-sm-5":"_uC7a4","p-sm-5":"_QmBt-","m-sm-6":"_E1Mby","p-sm-6":"_2b-XB","m-sm-7":"_shE-L","p-sm-7":"_DZiE4","m-sm-8":"_VrAWC","p-sm-8":"_wBvI1","m-sm-9":"_nT5hR","p-sm-9":"_utagJ","m-sm-10":"_suXWh","p-sm-10":"_HqYvz","ml-sm-0":"_XKTYr","pl-sm-0":"_-69hr","ml-sm-1":"_EnQBB","pl-sm-1":"_OBDbZ","ml-sm-2":"_4uEtT","pl-sm-2":"_jxnnV","ml-sm-3":"_4dyy7","pl-sm-3":"_fcp9m","ml-sm-4":"_Su7vR","pl-sm-4":"_iGsH0","ml-sm-5":"_j5bIe","pl-sm-5":"_iiiem","ml-sm-6":"_OSCRC","pl-sm-6":"_a6Ij8","ml-sm-7":"_cESaw","pl-sm-7":"_PZAKp","ml-sm-8":"_u1e7W","pl-sm-8":"_MMjU3","ml-sm-9":"_AEo-h","pl-sm-9":"_g6Qm-","ml-sm-10":"_RSzPa","pl-sm-10":"_5lIjk","mr-sm-0":"_iq-8E","pr-sm-0":"_6e7Oo","mr-sm-1":"_eRKuI","pr-sm-1":"_HaKr0","mr-sm-2":"_sl8Y1","pr-sm-2":"_-eAaQ","mr-sm-3":"_hqMXk","pr-sm-3":"_-nPxR","mr-sm-4":"_PFLZC","pr-sm-4":"_bz8kn","mr-sm-5":"_IDOFd","pr-sm-5":"_YphYe","mr-sm-6":"_DFP8e","pr-sm-6":"_uffDW","mr-sm-7":"_K4gIP","pr-sm-7":"_mFIwC","mr-sm-8":"_ZnbXS","pr-sm-8":"_SJ5sd","mr-sm-9":"_I4pB1","pr-sm-9":"_7Be-A","mr-sm-10":"_BlQpX","pr-sm-10":"_zoDIK","mt-sm-0":"_PpfXn","pt-sm-0":"_MLL-T","mt-sm-1":"_sFLGW","pt-sm-1":"_cssHG","mt-sm-2":"_-CEBu","pt-sm-2":"_3164x","mt-sm-3":"_Q-yYF","pt-sm-3":"_aSP--","mt-sm-4":"_HCnTw","pt-sm-4":"_pkP3Z","mt-sm-5":"_wpfr4","pt-sm-5":"_090aI","mt-sm-6":"_1YJBr","pt-sm-6":"_jYnK3","mt-sm-7":"_1f6jW","pt-sm-7":"_yPAeS","mt-sm-8":"_dNsVh","pt-sm-8":"_5n5Di","mt-sm-9":"_4RACJ","pt-sm-9":"_lgByZ","mt-sm-10":"_SBIrM","pt-sm-10":"_YsBHF","mb-sm-0":"_-z2mG","pb-sm-0":"_uz-nV","mb-sm-1":"_bPTQE","pb-sm-1":"_qTKZa","mb-sm-2":"_DWs80","pb-sm-2":"_7Pkyh","mb-sm-3":"_c5AbJ","pb-sm-3":"_0BQOV","mb-sm-4":"_V1gvx","pb-sm-4":"_s56MP","mb-sm-5":"_abqTk","pb-sm-5":"_YVQSJ","mb-sm-6":"_URgQP","pb-sm-6":"_3cTRb","mb-sm-7":"_tbZgH","pb-sm-7":"_oprz5","mb-sm-8":"_1tsmq","pb-sm-8":"_rp6hG","mb-sm-9":"_HI-9l","pb-sm-9":"_FnTxa","mb-sm-10":"_RHo4c","pb-sm-10":"_PZUR0","mx-sm-0":"_AvLmU","px-sm-0":"_PtUTn","mx-sm-1":"_WdN3k","px-sm-1":"_ytExi","mx-sm-2":"_RWE1V","px-sm-2":"_vw43o","mx-sm-3":"_Cbm5H","px-sm-3":"_P-Cf6","mx-sm-4":"_pjfwR","px-sm-4":"_WtStS","mx-sm-5":"_9a-hP","px-sm-5":"_JMDVm","mx-sm-6":"_OS2my","px-sm-6":"_-GrIc","mx-sm-7":"_BCsIq","px-sm-7":"_WhZnF","mx-sm-8":"_1aj2C","px-sm-8":"_THUvZ","mx-sm-9":"_JGLYJ","px-sm-9":"_A-5uV","mx-sm-10":"_va7ij","px-sm-10":"_XIozX","my-sm-0":"_iYNXh","py-sm-0":"_A4kOE","my-sm-1":"_vMEVL","py-sm-1":"_Xp5yf","my-sm-2":"_VXJt9","py-sm-2":"_LFrIT","my-sm-3":"_KkriU","py-sm-3":"_5locy","my-sm-4":"_FbhGv","py-sm-4":"_lhkY0","my-sm-5":"_iI3NO","py-sm-5":"_YPAWx","my-sm-6":"_nsvtx","py-sm-6":"_fzqyr","my-sm-7":"_HCcpN","py-sm-7":"_yw1CE","my-sm-8":"_FvOZ-","py-sm-8":"_H3dR0","my-sm-9":"_AgJBK","py-sm-9":"_1xEHl","my-sm-10":"_TkKl8","py-sm-10":"_B3-Sv","m-lg-0":"_67BGW","p-lg-0":"_hLq7o","m-lg-1":"_7N5-4","p-lg-1":"_b-vnX","m-lg-2":"_7k7Vs","p-lg-2":"_Q2FO1","m-lg-3":"_ZTSBV","p-lg-3":"_rstCV","m-lg-4":"_juJVU","p-lg-4":"_-F4s-","m-lg-5":"_e2ifF","p-lg-5":"_U4gTc","m-lg-6":"_-4I70","p-lg-6":"_VEcMB","m-lg-7":"_LQqFi","p-lg-7":"_eX0vJ","m-lg-8":"_qPpjy","p-lg-8":"_Bhr2u","m-lg-9":"_DPuMB","p-lg-9":"_H5C3L","m-lg-10":"_1EPHM","p-lg-10":"_mVFm8","ml-lg-0":"_jLUCv","pl-lg-0":"_sdQR4","ml-lg-1":"_dO-42","pl-lg-1":"_RDmEN","ml-lg-2":"_zilrt","pl-lg-2":"_3ZlSF","ml-lg-3":"_ywVg2","pl-lg-3":"_EVjPm","ml-lg-4":"_PBq8D","pl-lg-4":"_-NNsy","ml-lg-5":"_EKt1p","pl-lg-5":"_AAU--","ml-lg-6":"_M3D3t","pl-lg-6":"_VXvIT","ml-lg-7":"_yRT7X","pl-lg-7":"_0XsPz","ml-lg-8":"_HazA-","pl-lg-8":"_JZBh1","ml-lg-9":"_BU6zu","pl-lg-9":"_UPban","ml-lg-10":"_17FL5","pl-lg-10":"_mtv0u","mr-lg-0":"_YxsrA","pr-lg-0":"_1Wmvv","mr-lg-1":"_cQiRZ","pr-lg-1":"_9oheo","mr-lg-2":"_hwBOD","pr-lg-2":"_3i51G","mr-lg-3":"_iwzVs","pr-lg-3":"_1oRFN","mr-lg-4":"_3NfjL","pr-lg-4":"_9OJfa","mr-lg-5":"_YokXX","pr-lg-5":"_owhix","mr-lg-6":"_5Grwo","pr-lg-6":"_r0hct","mr-lg-7":"_dSkGq","pr-lg-7":"_mohcn","mr-lg-8":"_NUor1","pr-lg-8":"_Sjzdu","mr-lg-9":"_Py-ID","pr-lg-9":"_cZdrq","mr-lg-10":"_Etrwm","pr-lg-10":"_YCNLZ","mt-lg-0":"_qEoXx","pt-lg-0":"_nAw0E","mt-lg-1":"_pzDlL","pt-lg-1":"_h8g2E","mt-lg-2":"_QBTy7","pt-lg-2":"_IQoFB","mt-lg-3":"_UGd6N","pt-lg-3":"_PBzO5","mt-lg-4":"_SCRsd","pt-lg-4":"_aFnaW","mt-lg-5":"_EvBB0","pt-lg-5":"_U2iJt","mt-lg-6":"_5mahS","pt-lg-6":"_bzKPx","mt-lg-7":"_E-ZPb","pt-lg-7":"_bspLi","mt-lg-8":"_UA8zu","pt-lg-8":"_3KcAQ","mt-lg-9":"_Ze8Hr","pt-lg-9":"_FKo1d","mt-lg-10":"_kIAZR","pt-lg-10":"_j2qBV","mb-lg-0":"_u8RSu","pb-lg-0":"_7uW-o","mb-lg-1":"_1MnIg","pb-lg-1":"_EeRag","mb-lg-2":"_D-pBo","pb-lg-2":"_TTAjX","mb-lg-3":"_ZldL-","pb-lg-3":"_racM9","mb-lg-4":"_bv3JQ","pb-lg-4":"_m4wR1","mb-lg-5":"_G-OW-","pb-lg-5":"_hTxUe","mb-lg-6":"_-r5uZ","pb-lg-6":"_Ri3sL","mb-lg-7":"_yay34","pb-lg-7":"_H0ybB","mb-lg-8":"_kWcyP","pb-lg-8":"_f7KWI","mb-lg-9":"_f-ap7","pb-lg-9":"_piZbL","mb-lg-10":"_7GnNB","pb-lg-10":"_8RaRH","mx-lg-0":"_E0yc1","px-lg-0":"_jXVGt","mx-lg-1":"_SLGEt","px-lg-1":"_sfpGl","mx-lg-2":"_TQdd5","px-lg-2":"_ZH4kx","mx-lg-3":"_ByYI-","px-lg-3":"_sa8fi","mx-lg-4":"_QFKsD","px-lg-4":"_S-owE","mx-lg-5":"_T-3pC","px-lg-5":"_592xL","mx-lg-6":"_nHCUC","px-lg-6":"_SHGGx","mx-lg-7":"_u8AGu","px-lg-7":"_HGDmM","mx-lg-8":"_DoojR","px-lg-8":"_-xfYS","mx-lg-9":"_a39AX","px-lg-9":"_otRdO","mx-lg-10":"_0jC0L","px-lg-10":"_o3uqZ","my-lg-0":"_fWar-","py-lg-0":"_1KGBQ","my-lg-1":"_rmTwc","py-lg-1":"_SNpLr","my-lg-2":"_nZTm1","py-lg-2":"_fo45m","my-lg-3":"_DP8Cy","py-lg-3":"_tWJ-N","my-lg-4":"_Unnfi","py-lg-4":"_TJ7sH","my-lg-5":"_xIoEe","py-lg-5":"_CH2tq","my-lg-6":"_-4iWK","py-lg-6":"_di773","my-lg-7":"_ZAyLn","py-lg-7":"_yxUoV","my-lg-8":"_AAbVi","py-lg-8":"_ijW6K","my-lg-9":"_2SOZJ","py-lg-9":"_904so","my-lg-10":"_RuT9u","py-lg-10":"_bM8I-","m-md-0":"_JzJkX","p-md-0":"_f8nh5","m-md-1":"_hBYXm","p-md-1":"_X1BU4","m-md-2":"_dnzT0","p-md-2":"_5y79V","m-md-3":"_eBoSQ","p-md-3":"_3KIpC","m-md-4":"_J4f2b","p-md-4":"_-GqF4","m-md-5":"_FaWzb","p-md-5":"_c-ssZ","m-md-6":"_R6a8a","p-md-6":"_3OwFZ","m-md-7":"_VwKx8","p-md-7":"_oICA0","m-md-8":"_D9SPT","p-md-8":"_WqmxK","m-md-9":"_K-6kd","p-md-9":"_RNgLg","m-md-10":"_X2S0Q","p-md-10":"_dUN9U","ml-md-0":"_KI-v9","pl-md-0":"_J-z-m","ml-md-1":"_40NhZ","pl-md-1":"_dwk56","ml-md-2":"_-gtX1","pl-md-2":"_M-jkC","ml-md-3":"_bbVwf","pl-md-3":"_RzC-C","ml-md-4":"_QmHWn","pl-md-4":"_2Lg6Z","ml-md-5":"_TwW16","pl-md-5":"_NvcKj","ml-md-6":"_wxYmK","pl-md-6":"_Ay-nQ","ml-md-7":"_7FRbu","pl-md-7":"_zQusT","ml-md-8":"_f0-0U","pl-md-8":"_rE4oG","ml-md-9":"_8sF7X","pl-md-9":"_RPdXC","ml-md-10":"_-3o3f","pl-md-10":"_JN4N-","mr-md-0":"_5uGyZ","pr-md-0":"_sHCfS","mr-md-1":"_T-APd","pr-md-1":"_EWggr","mr-md-2":"_-IV1z","pr-md-2":"_Mj-hm","mr-md-3":"_W4jcg","pr-md-3":"_rACeA","mr-md-4":"_NVp7l","pr-md-4":"_6LfUe","mr-md-5":"_VyxVE","pr-md-5":"_edWIM","mr-md-6":"_FZQF6","pr-md-6":"_LJpMl","mr-md-7":"_xwZ6q","pr-md-7":"_YflPE","mr-md-8":"_RiPLi","pr-md-8":"_HZNIr","mr-md-9":"_2ZsDj","pr-md-9":"_XBBrF","mr-md-10":"_-kG-G","pr-md-10":"_co3mI","mt-md-0":"_e6IV0","pt-md-0":"_eLP-0","mt-md-1":"_27KXx","pt-md-1":"_gQ2-y","mt-md-2":"_L6-1Q","pt-md-2":"_x7jaE","mt-md-3":"_r5yZl","pt-md-3":"_jTIZh","mt-md-4":"_TxxTT","pt-md-4":"_znNzQ","mt-md-5":"_aPml3","pt-md-5":"_o-WiU","mt-md-6":"_ePcku","pt-md-6":"_7PW3g","mt-md-7":"_qOy4h","pt-md-7":"_czEFY","mt-md-8":"_C5QNJ","pt-md-8":"_LXyGu","mt-md-9":"_BzCq8","pt-md-9":"_j6x7Q","mt-md-10":"_RyoZo","pt-md-10":"_HO6RH","mb-md-0":"_dBqjR","pb-md-0":"_Vtnl-","mb-md-1":"_4CIBM","pb-md-1":"_nkuIR","mb-md-2":"_jioqc","pb-md-2":"_KVlPp","mb-md-3":"_NVp7e","pb-md-3":"_51YgW","mb-md-4":"_6ZjBo","pb-md-4":"_5ewFY","mb-md-5":"_ttOxi","pb-md-5":"_VLqqB","mb-md-6":"_Vl1FM","pb-md-6":"_lj2o-","mb-md-7":"_fY9Hi","pb-md-7":"_vdqU0","mb-md-8":"_MBU95","pb-md-8":"_z-gSw","mb-md-9":"_Wr9dI","pb-md-9":"_D1Q7Q","mb-md-10":"_-resl","pb-md-10":"_Jamfb","mx-md-0":"_0r9rS","px-md-0":"_epgof","mx-md-1":"_aYYOb","px-md-1":"_g6u4x","mx-md-2":"_v6Jsi","px-md-2":"_e7lpx","mx-md-3":"_B5OpT","px-md-3":"_IJdST","mx-md-4":"_Vgq7O","px-md-4":"_q2tQ3","mx-md-5":"_udCxk","px-md-5":"_dkBwz","mx-md-6":"_HAAdk","px-md-6":"_gxcpD","mx-md-7":"_-wAGl","px-md-7":"_PBj8s","mx-md-8":"_51YzX","px-md-8":"_Ht55U","mx-md-9":"_UaNNS","px-md-9":"_A3qbp","mx-md-10":"_O9ir-","px-md-10":"_2jJOG","my-md-0":"_p6EfS","py-md-0":"_8FGSM","my-md-1":"_T6eYc","py-md-1":"_Jrh0m","my-md-2":"_oxohh","py-md-2":"_DTd2y","my-md-3":"_iGc8i","py-md-3":"_jqXbJ","my-md-4":"_wCkrO","py-md-4":"_4p1cz","my-md-5":"_m1Dhe","py-md-5":"_UFngr","my-md-6":"_FdWqj","py-md-6":"_utySi","my-md-7":"_TsKhD","py-md-7":"_8aght","my-md-8":"_H2tbJ","py-md-8":"_mX8dL","my-md-9":"_YP8V8","py-md-9":"_GEd1x","my-md-10":"_qnAfn","py-md-10":"_3MK3a","m-xl-0":"_QUeDl","p-xl-0":"_tpnY6","m-xl-1":"_JhTnG","p-xl-1":"_leasc","m-xl-2":"_rj70q","p-xl-2":"_nmUas","m-xl-3":"_SH7Mw","p-xl-3":"_2sLhA","m-xl-4":"_WKBLF","p-xl-4":"_JTlZn","m-xl-5":"_keccz","p-xl-5":"_W2L9n","m-xl-6":"_MmHtB","p-xl-6":"_16QWL","m-xl-7":"_qoXt8","p-xl-7":"_aZTjp","m-xl-8":"_Gl1CI","p-xl-8":"_u033S","m-xl-9":"_fgyK-","p-xl-9":"_gWQMQ","m-xl-10":"_1zthQ","p-xl-10":"_jYgaz","ml-xl-0":"_Gc0yF","pl-xl-0":"_7r0qa","ml-xl-1":"_riQOx","pl-xl-1":"_ZOf2r","ml-xl-2":"_aqpam","pl-xl-2":"_Kzewg","ml-xl-3":"_riGnY","pl-xl-3":"_fImnr","ml-xl-4":"_Spb-1","pl-xl-4":"_xqk-P","ml-xl-5":"_5ZrWS","pl-xl-5":"_KEqQd","ml-xl-6":"_6cdvz","pl-xl-6":"_RjL-8","ml-xl-7":"_hfyi5","pl-xl-7":"_-xSML","ml-xl-8":"_eHDDu","pl-xl-8":"_iDidv","ml-xl-9":"_Gg4Fk","pl-xl-9":"_fYSK3","ml-xl-10":"_1V3MM","pl-xl-10":"_4jnhK","mr-xl-0":"_8fnBs","pr-xl-0":"_Kemkd","mr-xl-1":"_4ox8d","pr-xl-1":"_zG2Y2","mr-xl-2":"_Pl4X2","pr-xl-2":"_EZpb9","mr-xl-3":"_efgC0","pr-xl-3":"_6hd7x","mr-xl-4":"_rp8M9","pr-xl-4":"_nsRMj","mr-xl-5":"_ayyUE","pr-xl-5":"_BHXMH","mr-xl-6":"_Ez0-c","pr-xl-6":"_xTS9o","mr-xl-7":"_KlyJI","pr-xl-7":"_w3-5z","mr-xl-8":"_onnmv","pr-xl-8":"_aQYpF","mr-xl-9":"_D4mrX","pr-xl-9":"_-MGmg","mr-xl-10":"_KmLe8","pr-xl-10":"_rxxtP","mt-xl-0":"_0-1Kw","pt-xl-0":"_jVAPE","mt-xl-1":"_dO4nc","pt-xl-1":"_oFjXt","mt-xl-2":"_njinL","pt-xl-2":"_EVcqW","mt-xl-3":"_k7EwU","pt-xl-3":"_oALYA","mt-xl-4":"_SSrqv","pt-xl-4":"_8ZIWU","mt-xl-5":"_PXga1","pt-xl-5":"_ewSdF","mt-xl-6":"_0hKMy","pt-xl-6":"_fmLlw","mt-xl-7":"_aBbeO","pt-xl-7":"_Ghd-T","mt-xl-8":"_QILjg","pt-xl-8":"_4401M","mt-xl-9":"_mdFN4","pt-xl-9":"_uBjA3","mt-xl-10":"_l3TPU","pt-xl-10":"_hIKVS","mb-xl-0":"_2Et8-","pb-xl-0":"_uBGMl","mb-xl-1":"_AnrEX","pb-xl-1":"_-PMBv","mb-xl-2":"_cr4lU","pb-xl-2":"_BVCap","mb-xl-3":"_PgCvP","pb-xl-3":"_69b-5","mb-xl-4":"_x5odK","pb-xl-4":"_LzoYk","mb-xl-5":"_3q-dE","pb-xl-5":"_DXCCv","mb-xl-6":"_I8Qbz","pb-xl-6":"_wK-Lg","mb-xl-7":"_NXT2E","pb-xl-7":"_hw62n","mb-xl-8":"_w5tOT","pb-xl-8":"_KA9xk","mb-xl-9":"_B5jk4","pb-xl-9":"_N5trL","mb-xl-10":"_JU9c5","pb-xl-10":"_kP-VE","mx-xl-0":"_jgBYj","px-xl-0":"_iLny2","mx-xl-1":"_1PpJT","px-xl-1":"_eAdDk","mx-xl-2":"_v1kJm","px-xl-2":"_hU50M","mx-xl-3":"_L1JqU","px-xl-3":"_lGvGI","mx-xl-4":"_KNNj2","px-xl-4":"_6-Kgj","mx-xl-5":"_r9t8u","px-xl-5":"_pI6ok","mx-xl-6":"_YO3KF","px-xl-6":"_LIjtn","mx-xl-7":"_Yz-3Z","px-xl-7":"_7xWar","mx-xl-8":"_NEAsT","px-xl-8":"_KSLjS","mx-xl-9":"_XqTby","px-xl-9":"_y5sVK","mx-xl-10":"_GysTi","px-xl-10":"_udCRM","my-xl-0":"_iWpWS","py-xl-0":"_cyc8U","my-xl-1":"_IcUiN","py-xl-1":"_bfKBO","my-xl-2":"_IFTOa","py-xl-2":"_sXWH4","my-xl-3":"_TXATm","py-xl-3":"_Vt5Ze","my-xl-4":"_GLPso","py-xl-4":"_dI2DM","my-xl-5":"_twkBw","py-xl-5":"_m7dMy","my-xl-6":"_a-KcP","py-xl-6":"_dLwS8","my-xl-7":"_qkPb4","py-xl-7":"_uZaN7","my-xl-8":"_2btRT","py-xl-8":"_qsAt5","my-xl-9":"_LpPEh","py-xl-9":"_v1Rtf","my-xl-10":"_xKXEK","py-xl-10":"_pZt9W","background-shine":"_K7sQH","path":"_ko8i0","line":"_uhBI-","skeleton":"_tfiqH","skeleton-animate":"_haT-0","skeleton-dark":"_ZpubN","skeleton-dark-animate":"_BVbU1","skeleton-transparent":"_p-CoM","skeleton-transparent-animate":"_YElIQ","semi-transparent":"_wSCsK","no-margins":"_lNgTM","dots":"_igFzN","colored":"_iCiK-","small":"_zvmsC","extra-small":"_6tLmp","align-left":"_jJMP1","align-right":"_jpWFr"};
|
|
1161
|
+
var styles = {"container":"_a5RSj","m-0":"_wcoyj","p-0":"_Rd5sE","m-1":"_vAsxv","p-1":"_KZ14Z","m-2":"_Bvk20","p-2":"_Z7fhL","m-3":"_ZqDCi","p-3":"_CiGc9","m-4":"_8JK-L","p-4":"_FadoA","m-5":"_baCle","p-5":"_cvJqX","m-6":"_P3lUT","p-6":"_ZmGGH","m-7":"_5Tnff","p-7":"_-t7z9","m-8":"_1JxUQ","p-8":"_EATOh","m-9":"_pJGV0","p-9":"_B762d","m-10":"_Z3rGU","p-10":"_0ULM0","ml-0":"_ZSfG7","pl-0":"_hjKq0","ml-1":"_srwbU","pl-1":"_oz-sG","ml-2":"_RnILi","pl-2":"_-nAjZ","ml-3":"_lges5","pl-3":"_AwVsn","ml-4":"_p4arL","pl-4":"_upAfC","ml-5":"_-iEnG","pl-5":"_bqApa","ml-6":"_uF0xd","pl-6":"_WUUFo","ml-7":"_eXFZs","pl-7":"_ZforM","ml-8":"_2AA5o","pl-8":"_u6bF0","ml-9":"_19IxL","pl-9":"_vmLg6","ml-10":"_eyqFK","pl-10":"_mr36J","mr-0":"_6RpFz","pr-0":"_fgnzi","mr-1":"_fBjrA","pr-1":"_QrC2Y","mr-2":"_0Y6RC","pr-2":"_5kZkr","mr-3":"_iaZj-","pr-3":"_A-IQ7","mr-4":"_zBHHn","pr-4":"_pHQ4t","mr-5":"_1JTCi","pr-5":"_ODvHf","mr-6":"_n6Enr","pr-6":"_XNdBl","mr-7":"_YfaVy","pr-7":"_ZfZDh","mr-8":"_le3sp","pr-8":"_K1rvN","mr-9":"_mL6UV","pr-9":"_76mZn","mr-10":"_fo-sf","pr-10":"_xvsK1","mt-0":"_1uBdK","pt-0":"_30qg7","mt-1":"_NKlWd","pt-1":"_3yj99","mt-2":"_lLyiG","pt-2":"_GsXqc","mt-3":"_zC55o","pt-3":"_3OZTc","mt-4":"_v1VXO","pt-4":"_sFrdR","mt-5":"_qbBFx","pt-5":"_DQrF8","mt-6":"_dJG8P","pt-6":"_Zxte7","mt-7":"_sUtkp","pt-7":"_9B1r5","mt-8":"_Qrnld","pt-8":"_7xBV7","mt-9":"_OPKw2","pt-9":"_cXhnD","mt-10":"_m5k36","pt-10":"_b9nMR","mb-0":"_7AyaR","pb-0":"_u0vFs","mb-1":"_ivpnr","pb-1":"_1dkPf","mb-2":"_D8KfL","pb-2":"_wzuIB","mb-3":"_zwh5E","pb-3":"_rT8wS","mb-4":"_wpLh9","pb-4":"_AGvn8","mb-5":"_-phDB","pb-5":"_r4HWg","mb-6":"_RZI7I","pb-6":"_-t4Gs","mb-7":"_Vw-z1","pb-7":"_qwD1B","mb-8":"_f3nKh","pb-8":"_Sk1Fn","mb-9":"_2MDAw","pb-9":"_jZbwv","mb-10":"_gRDI3","pb-10":"_W7OVZ","mx-0":"_I6mmv","px-0":"_BVRG5","mx-1":"_b7fTJ","px-1":"_bZf6u","mx-2":"_FoGd8","px-2":"_-vX3t","mx-3":"_0H3Cj","loading-dots":"_pnWN9","big":"_k0j0g","dot":"_9LXS6","px-3":"_ggifM","mx-4":"_leP-v","px-4":"_7Kcun","mx-5":"_sBxud","px-5":"_oJO7X","mx-6":"_F-Una","px-6":"_Bl-A8","mx-7":"_NVWrT","px-7":"_Bn6vR","mx-8":"_OK9GD","px-8":"_iv1v2","mx-9":"_WVtLI","px-9":"_ntW3v","mx-10":"_21M1-","px-10":"_BjXo-","my-0":"_3v8p4","py-0":"_EBmV-","my-1":"_DcOh5","py-1":"_udCfN","my-2":"_Lk0RK","py-2":"_wIENK","my-3":"_nsOf5","py-3":"_No-oK","my-4":"_L1ycy","py-4":"_42DgQ","my-5":"_3bZSh","py-5":"_XjTMT","my-6":"_Ty-GG","py-6":"_7Dr5E","my-7":"_PILBF","py-7":"_vyvsk","my-8":"_AkmkO","py-8":"_X0xg-","my-9":"_VSxcf","py-9":"_Hq-yp","my-10":"_FxwFR","py-10":"_BnN2G","m-sm-0":"_EZHgz","p-sm-0":"_oKPqX","m-sm-1":"_gdysh","p-sm-1":"_-7KK8","m-sm-2":"_BLBqZ","p-sm-2":"_IgrmD","m-sm-3":"_zbrA7","p-sm-3":"_eimPJ","m-sm-4":"_7RaaP","p-sm-4":"_0-Cvi","m-sm-5":"_PhCOs","p-sm-5":"_CIKFA","m-sm-6":"_8WC3P","p-sm-6":"_2FqT5","m-sm-7":"_D7sDK","p-sm-7":"_L3EpN","m-sm-8":"_mG6-A","p-sm-8":"_D2Gx9","m-sm-9":"_2tGFi","p-sm-9":"_8n6VA","m-sm-10":"_G-MMA","p-sm-10":"_pgJzb","ml-sm-0":"_BF125","pl-sm-0":"_81k6q","ml-sm-1":"_JSywC","pl-sm-1":"_1eoeN","ml-sm-2":"_q5cSf","pl-sm-2":"_A6Li5","ml-sm-3":"_-7C1-","pl-sm-3":"_lBIcx","ml-sm-4":"_bvj1N","pl-sm-4":"_SQmLb","ml-sm-5":"_pwy33","pl-sm-5":"_iuqDu","ml-sm-6":"_CNiYb","pl-sm-6":"_tQ2J-","ml-sm-7":"_Zuec-","pl-sm-7":"_iJF5q","ml-sm-8":"_lP-ls","pl-sm-8":"_1xIwn","ml-sm-9":"_Jld-6","pl-sm-9":"_pfOVT","ml-sm-10":"_TGfAU","pl-sm-10":"_MYgNP","mr-sm-0":"_CEiPO","pr-sm-0":"_rZ5kD","mr-sm-1":"_-bdjz","pr-sm-1":"_nRBRL","mr-sm-2":"_17O3E","pr-sm-2":"_uBaUj","mr-sm-3":"_5oO1d","pr-sm-3":"_jQWXm","mr-sm-4":"_peO4Y","pr-sm-4":"_P2f3n","mr-sm-5":"_AUc3q","pr-sm-5":"_7TR99","mr-sm-6":"_e3d5h","pr-sm-6":"_QqPl1","mr-sm-7":"_Yc1cR","pr-sm-7":"_5fd5H","mr-sm-8":"_z4g3-","pr-sm-8":"_gY3T9","mr-sm-9":"_P9chP","pr-sm-9":"_G4k6A","mr-sm-10":"_QWMD2","pr-sm-10":"_2POWv","mt-sm-0":"_xYS0m","pt-sm-0":"_gYD3b","mt-sm-1":"_08rCY","pt-sm-1":"_UsP-z","mt-sm-2":"_HWQec","pt-sm-2":"_cjIgZ","mt-sm-3":"_gZV20","pt-sm-3":"_zJZJm","mt-sm-4":"_4FE5W","pt-sm-4":"_VpJhv","mt-sm-5":"_eu88H","pt-sm-5":"_V7qwC","mt-sm-6":"_09LyL","pt-sm-6":"_n8bba","mt-sm-7":"_q-ASs","pt-sm-7":"_3b9S8","mt-sm-8":"_iTxDv","pt-sm-8":"_--Hdk","mt-sm-9":"_OaVE-","pt-sm-9":"_cd-UP","mt-sm-10":"_ZIr-H","pt-sm-10":"_6jYkb","mb-sm-0":"_YgN23","pb-sm-0":"_F8F6A","mb-sm-1":"_4QH8u","pb-sm-1":"_A2I8v","mb-sm-2":"_jrZxh","pb-sm-2":"_2VL1m","mb-sm-3":"_IB8uX","pb-sm-3":"_meger","mb-sm-4":"_-gCVy","pb-sm-4":"_R-qMm","mb-sm-5":"_YwvXh","pb-sm-5":"_pyu5v","mb-sm-6":"_qwhTX","pb-sm-6":"_l61nc","mb-sm-7":"_0KfeR","pb-sm-7":"_YXuNM","mb-sm-8":"_75fJr","pb-sm-8":"_jp-Qe","mb-sm-9":"_V0wM-","pb-sm-9":"_vMKpc","mb-sm-10":"_sxEY8","pb-sm-10":"_k2mOD","mx-sm-0":"_qPF9i","px-sm-0":"_cl18e","mx-sm-1":"_LvMUO","px-sm-1":"_RcIbD","mx-sm-2":"_xLMK5","px-sm-2":"_GCL1S","mx-sm-3":"_t0q4o","px-sm-3":"_cuiW8","mx-sm-4":"_pHjdd","px-sm-4":"_LDu1N","mx-sm-5":"_cYpLd","px-sm-5":"_24Yji","mx-sm-6":"_pXCM6","px-sm-6":"_uoSDI","mx-sm-7":"_qbXoQ","px-sm-7":"_0RlFx","mx-sm-8":"_3f-s3","px-sm-8":"_xAS-y","mx-sm-9":"_vx3dZ","px-sm-9":"_ndXm6","mx-sm-10":"_6lFsZ","px-sm-10":"_EZ6Qr","my-sm-0":"_0t-Cg","py-sm-0":"_N3sbI","my-sm-1":"_jdmVi","py-sm-1":"_GlMKb","my-sm-2":"_yFRs8","py-sm-2":"_LNRZQ","my-sm-3":"_qSuwF","py-sm-3":"_ON3vd","my-sm-4":"_1h6pX","py-sm-4":"_TZg0h","my-sm-5":"_vT295","py-sm-5":"_J1zu8","my-sm-6":"_7LuwF","py-sm-6":"_qbbdP","my-sm-7":"_9vKuY","py-sm-7":"_C9gzg","my-sm-8":"_Nx1-S","py-sm-8":"_8lmEZ","my-sm-9":"_LxGxx","py-sm-9":"_3y4jy","my-sm-10":"_61y0C","py-sm-10":"_fxmBa","m-lg-0":"_PJydR","p-lg-0":"_3o1cd","m-lg-1":"_MAdi8","p-lg-1":"_JDc1V","m-lg-2":"_pEH2Q","p-lg-2":"_Ic9tL","m-lg-3":"_P6cGt","p-lg-3":"_nzx-g","m-lg-4":"_9bH-j","p-lg-4":"_KdZTF","m-lg-5":"_hxjaE","p-lg-5":"_LDamm","m-lg-6":"_xXtVI","p-lg-6":"_ZWhTL","m-lg-7":"_mPnVe","p-lg-7":"_lJnBT","m-lg-8":"_J-MQA","p-lg-8":"_MGs60","m-lg-9":"_ixlkP","p-lg-9":"_Lru1O","m-lg-10":"_eH6ht","p-lg-10":"_ugZzQ","ml-lg-0":"_6z3OE","pl-lg-0":"_PDwaH","ml-lg-1":"_GMcN9","pl-lg-1":"_NoDTB","ml-lg-2":"_PQ5AQ","pl-lg-2":"_8-Pu6","ml-lg-3":"_PktVo","pl-lg-3":"_JTpEZ","ml-lg-4":"_zIcyb","pl-lg-4":"_e4YYS","ml-lg-5":"_HrlcA","pl-lg-5":"_cwG3s","ml-lg-6":"_Xf7Wv","pl-lg-6":"_XYe-E","ml-lg-7":"_vnGAT","pl-lg-7":"_NnScU","ml-lg-8":"_9z7Tj","pl-lg-8":"_DDEn1","ml-lg-9":"_sxSQp","pl-lg-9":"_-r39c","ml-lg-10":"_kppNg","pl-lg-10":"_1mgTz","mr-lg-0":"_WtS6h","pr-lg-0":"_NKNU9","mr-lg-1":"_kxaOQ","pr-lg-1":"_8OvSK","mr-lg-2":"_bTQt8","pr-lg-2":"_yQerd","mr-lg-3":"_f7H9P","pr-lg-3":"_Kwfoa","mr-lg-4":"_lkF5d","pr-lg-4":"_b7tIe","mr-lg-5":"_H4e5a","pr-lg-5":"_pxwBt","mr-lg-6":"_I81Hq","pr-lg-6":"_1Xwlf","mr-lg-7":"_gtO-Y","pr-lg-7":"_vjm3Z","mr-lg-8":"_ZfEr1","pr-lg-8":"_je0MD","mr-lg-9":"_h43Co","pr-lg-9":"_Lnbh5","mr-lg-10":"_HoaFo","pr-lg-10":"_3puFA","mt-lg-0":"_a7pDC","pt-lg-0":"_-URo7","mt-lg-1":"_Ot-U1","pt-lg-1":"_rMvMf","mt-lg-2":"_95ooz","pt-lg-2":"_vkEgW","mt-lg-3":"_9XqX5","pt-lg-3":"_b-oGK","mt-lg-4":"_eYL6E","pt-lg-4":"_zgUGu","mt-lg-5":"_BqCaM","pt-lg-5":"_Y9PL4","mt-lg-6":"_0aOPb","pt-lg-6":"_nvMum","mt-lg-7":"_-pNQS","pt-lg-7":"_funa7","mt-lg-8":"_sDweY","pt-lg-8":"_S1hmu","mt-lg-9":"_Y4BpR","pt-lg-9":"_J3203","mt-lg-10":"_Jm-dV","pt-lg-10":"_0WSnk","mb-lg-0":"_4uWrE","pb-lg-0":"_qy0mb","mb-lg-1":"_jUNMm","pb-lg-1":"_pUTOt","mb-lg-2":"_aGqhb","pb-lg-2":"_xAKUw","mb-lg-3":"_kado5","pb-lg-3":"_htLgL","mb-lg-4":"_mWWU1","pb-lg-4":"_gqGWe","mb-lg-5":"_ceycQ","pb-lg-5":"_RzNIh","mb-lg-6":"_VLMG4","pb-lg-6":"_-8-Aj","mb-lg-7":"_HBnRW","pb-lg-7":"_ZsIEk","mb-lg-8":"_bj2r5","pb-lg-8":"_cBnkL","mb-lg-9":"_w3001","pb-lg-9":"_Kua-7","mb-lg-10":"_wZKsF","pb-lg-10":"_GX-Wb","mx-lg-0":"_-G50w","px-lg-0":"_OTPQl","mx-lg-1":"_JJ9DD","px-lg-1":"_jedSl","mx-lg-2":"_n9zl3","px-lg-2":"_Cg-Eq","mx-lg-3":"_6ZhL3","px-lg-3":"_0oPYT","mx-lg-4":"_H9sE1","px-lg-4":"_aV3U2","mx-lg-5":"_Y2BNn","px-lg-5":"_EzRHI","mx-lg-6":"_fp9jU","px-lg-6":"_wghWL","mx-lg-7":"_zB5Qv","px-lg-7":"_gclTH","mx-lg-8":"_Qxq2x","px-lg-8":"_M7akr","mx-lg-9":"_Vfhs-","px-lg-9":"_Rv7m-","mx-lg-10":"_lni3U","px-lg-10":"_gdVVu","my-lg-0":"_rMuza","py-lg-0":"_MZopi","my-lg-1":"_Thq10","py-lg-1":"_BXQnr","my-lg-2":"_lHRe-","py-lg-2":"_6MiWj","my-lg-3":"_-vobr","py-lg-3":"_xtsGY","my-lg-4":"_m2hen","py-lg-4":"_-069Y","my-lg-5":"_nTHzp","py-lg-5":"_r4pes","my-lg-6":"_LTkTV","py-lg-6":"_13TOF","my-lg-7":"_Em859","py-lg-7":"_mJzVF","my-lg-8":"_T8EVh","py-lg-8":"_h2ol5","my-lg-9":"_T-LZJ","py-lg-9":"_QIrnZ","my-lg-10":"_EY0q7","py-lg-10":"_hvU2E","m-md-0":"_u8Not","p-md-0":"_PVgyV","m-md-1":"_y-q4l","p-md-1":"_7X2x6","m-md-2":"_r3IGF","p-md-2":"_abaFH","m-md-3":"_rH0Ca","p-md-3":"_m-V0R","m-md-4":"_NQj-k","p-md-4":"_xjGzA","m-md-5":"_41oza","p-md-5":"_cgf0Z","m-md-6":"_lelHI","p-md-6":"_2C8Oa","m-md-7":"_w1Eil","p-md-7":"_EtEzw","m-md-8":"_Q8a3D","p-md-8":"_5nsxA","m-md-9":"_LzOqJ","p-md-9":"_7LEzr","m-md-10":"_CRNVR","p-md-10":"_ClNNV","ml-md-0":"_3abXz","pl-md-0":"_7BRiC","ml-md-1":"_Jbe0V","pl-md-1":"_Ok-KV","ml-md-2":"_5SO4-","pl-md-2":"_IvJ88","ml-md-3":"_NWLcP","pl-md-3":"_XASwl","ml-md-4":"_La-jA","pl-md-4":"_M8vz7","ml-md-5":"_4UZJ3","pl-md-5":"_ikRMN","ml-md-6":"_GpxKA","pl-md-6":"_-vp4I","ml-md-7":"_qZisY","pl-md-7":"_UspU5","ml-md-8":"_xTqIg","pl-md-8":"_8LLNG","ml-md-9":"_u3Wi6","pl-md-9":"_yukVA","ml-md-10":"_BJ3Kb","pl-md-10":"_Ncx33","mr-md-0":"_-dCKD","pr-md-0":"_p4Xdb","mr-md-1":"_DZXj3","pr-md-1":"_YZLPN","mr-md-2":"_IEATr","pr-md-2":"_OGWz-","mr-md-3":"_UxMFq","pr-md-3":"_qMmI8","mr-md-4":"_KEt4t","pr-md-4":"_nLevO","mr-md-5":"_AMVPM","pr-md-5":"_3m10o","mr-md-6":"_fzrrj","pr-md-6":"_Wbjjj","mr-md-7":"_mZWrU","pr-md-7":"_mqIEB","mr-md-8":"_5p3E5","pr-md-8":"_NyrXd","mr-md-9":"_gez8y","pr-md-9":"_qLaJ8","mr-md-10":"_TR-zd","pr-md-10":"_SqjX-","mt-md-0":"_Okpug","pt-md-0":"_xIGF1","mt-md-1":"_kkttT","pt-md-1":"_6Fntk","mt-md-2":"_jjt3V","pt-md-2":"_MBCLj","mt-md-3":"_AcRpn","pt-md-3":"_9e4N3","mt-md-4":"_TEYLB","pt-md-4":"_G-b2b","mt-md-5":"_ADYmJ","pt-md-5":"_-PW2W","mt-md-6":"_IiBSh","pt-md-6":"_oOMKv","mt-md-7":"_LzMKR","pt-md-7":"_P93Fk","mt-md-8":"_ZPChR","pt-md-8":"_IjZLd","mt-md-9":"_OYWyj","pt-md-9":"_6VDq-","mt-md-10":"_NFUMl","pt-md-10":"_9N5Rs","mb-md-0":"_KNEZM","pb-md-0":"_dlRtG","mb-md-1":"_2TJCJ","pb-md-1":"_FYJz-","mb-md-2":"_p5q4o","pb-md-2":"_W20R5","mb-md-3":"_QMtNI","pb-md-3":"_X4ghG","mb-md-4":"_ROsHu","pb-md-4":"_LLz3c","mb-md-5":"_FKlov","pb-md-5":"_qQje5","mb-md-6":"_zHdHt","pb-md-6":"_7vH68","mb-md-7":"_5-25V","pb-md-7":"_a5Jzs","mb-md-8":"_9NPzr","pb-md-8":"_NpZZI","mb-md-9":"_F5182","pb-md-9":"_2SM3y","mb-md-10":"_R3bvz","pb-md-10":"_Sop9H","mx-md-0":"_OUA4d","px-md-0":"_s-0Qy","mx-md-1":"_cBeVb","px-md-1":"_34ZBT","mx-md-2":"_-dnuU","px-md-2":"_0-UrT","mx-md-3":"_CewFh","px-md-3":"_ZrFGb","mx-md-4":"_5Cvub","px-md-4":"_9CpfN","mx-md-5":"_O1iRv","px-md-5":"_dnUx0","mx-md-6":"_QmgAB","px-md-6":"_vP8Uq","mx-md-7":"_PcZ9w","px-md-7":"_5l4s3","mx-md-8":"_isOXv","px-md-8":"_cvHBA","mx-md-9":"_LIRJ0","px-md-9":"_09Gju","mx-md-10":"_b-TCB","px-md-10":"_5UoX8","my-md-0":"_wrYvU","py-md-0":"_Fwd2A","my-md-1":"_vBwvs","py-md-1":"_vl1f-","my-md-2":"_7g5va","py-md-2":"_D0JS-","my-md-3":"_8DN0N","py-md-3":"_qvNWl","my-md-4":"_o5Mnp","py-md-4":"_rFfbU","my-md-5":"_7XZDH","py-md-5":"_1B0Rr","my-md-6":"_s09ut","py-md-6":"_jnQRw","my-md-7":"_PgL1b","py-md-7":"_T4779","my-md-8":"_R0d0D","py-md-8":"_Fkri-","my-md-9":"_FM-XK","py-md-9":"_H-snR","my-md-10":"_Z49JP","py-md-10":"_01G-F","m-xl-0":"_H4Jyh","p-xl-0":"_WMtq9","m-xl-1":"_FeJPz","p-xl-1":"_00t3r","m-xl-2":"_svLno","p-xl-2":"_2C22I","m-xl-3":"_T5qYA","p-xl-3":"_dcbn0","m-xl-4":"_Xl89-","p-xl-4":"_Ctf-y","m-xl-5":"_ZWihS","p-xl-5":"_Lzig2","m-xl-6":"_jljvY","p-xl-6":"_X1owU","m-xl-7":"_l1myf","p-xl-7":"_T1fTP","m-xl-8":"_FWU16","p-xl-8":"_R2T50","m-xl-9":"_2TtzQ","p-xl-9":"_-142y","m-xl-10":"_975wG","p-xl-10":"_DPori","ml-xl-0":"_9Zc9q","pl-xl-0":"_On2x0","ml-xl-1":"_IryvS","pl-xl-1":"_J2DXA","ml-xl-2":"_S7cyp","pl-xl-2":"_YxNk9","ml-xl-3":"_6eyqg","pl-xl-3":"_cwyGU","ml-xl-4":"_TbIDB","pl-xl-4":"_JKpl8","ml-xl-5":"_P17BD","pl-xl-5":"_w8ccI","ml-xl-6":"_fUJPM","pl-xl-6":"_mgqKZ","ml-xl-7":"_p-Fn9","pl-xl-7":"_eMF8n","ml-xl-8":"_VO2-y","pl-xl-8":"_O9n-E","ml-xl-9":"_wqX4W","pl-xl-9":"_wDG35","ml-xl-10":"_-TNE-","pl-xl-10":"_jyfgf","mr-xl-0":"_9aQeC","pr-xl-0":"_zWCV0","mr-xl-1":"_vL8LJ","pr-xl-1":"_yA6nk","mr-xl-2":"_ZkYxH","pr-xl-2":"_-LN7c","mr-xl-3":"_WtPxc","pr-xl-3":"_Yn0F0","mr-xl-4":"_M1pzN","pr-xl-4":"_zX1qM","mr-xl-5":"_HWX7s","pr-xl-5":"_0jJWY","mr-xl-6":"_cG-JL","pr-xl-6":"_o2-cg","mr-xl-7":"_g0V5o","pr-xl-7":"_seBWS","mr-xl-8":"_1Rg0L","pr-xl-8":"_uZxYI","mr-xl-9":"_DJD2r","pr-xl-9":"_WXY42","mr-xl-10":"_-EWpt","pr-xl-10":"_W-0Oq","mt-xl-0":"_A3mxM","pt-xl-0":"_mG8-q","mt-xl-1":"_eSV7Q","pt-xl-1":"_9SJkd","mt-xl-2":"_mdspA","pt-xl-2":"_GloWk","mt-xl-3":"_Fn9WR","pt-xl-3":"_l5z5Q","mt-xl-4":"_J5EuO","pt-xl-4":"_z4jGH","mt-xl-5":"_SjIc3","pt-xl-5":"_inlrN","mt-xl-6":"_9iy8O","pt-xl-6":"_FGdNq","mt-xl-7":"_DNLb-","pt-xl-7":"_FDvwW","mt-xl-8":"_cB8Ua","pt-xl-8":"_sFRzN","mt-xl-9":"_iFCES","pt-xl-9":"_M8U3T","mt-xl-10":"_zrBn9","pt-xl-10":"_kYhdD","mb-xl-0":"_AVjLW","pb-xl-0":"_S2OiP","mb-xl-1":"_vnN5p","pb-xl-1":"_ukgJ7","mb-xl-2":"_wZjwy","pb-xl-2":"_rINJ4","mb-xl-3":"_MFAs1","pb-xl-3":"_BjtEQ","mb-xl-4":"_72dvy","pb-xl-4":"_bCnbX","mb-xl-5":"_Uyt6r","pb-xl-5":"_F3Ibd","mb-xl-6":"_iBLUD","pb-xl-6":"_yjY5Y","mb-xl-7":"_6TfYx","pb-xl-7":"_QdVWm","mb-xl-8":"_p-cIL","pb-xl-8":"_saIeH","mb-xl-9":"_Z3PUh","pb-xl-9":"_f8cy7","mb-xl-10":"_ndw2F","pb-xl-10":"_Yxkus","mx-xl-0":"_Bb6PX","px-xl-0":"_M5EOJ","mx-xl-1":"_iWwKY","px-xl-1":"_nWXZF","mx-xl-2":"_OCB5d","px-xl-2":"_GIqDO","mx-xl-3":"_mskG2","px-xl-3":"_-TVSP","mx-xl-4":"_rp-dc","px-xl-4":"_rfQsy","mx-xl-5":"_FgYNJ","px-xl-5":"_yA-K0","mx-xl-6":"_KJ12G","px-xl-6":"_gmhRk","mx-xl-7":"_XF8NS","px-xl-7":"_C0zhm","mx-xl-8":"_rOBd5","px-xl-8":"_FZf4w","mx-xl-9":"_GosX1","px-xl-9":"_zvpad","mx-xl-10":"_uU6kY","px-xl-10":"_XfRJ5","my-xl-0":"_zhez-","py-xl-0":"_-Dum7","my-xl-1":"_QvfIQ","py-xl-1":"_66V5-","my-xl-2":"_e84Sk","py-xl-2":"_pOej1","my-xl-3":"_uv8an","py-xl-3":"_6iY-g","my-xl-4":"_Ur6W4","py-xl-4":"_JPq7T","my-xl-5":"_3GKlh","py-xl-5":"_Gcnsl","my-xl-6":"_JrBH1","py-xl-6":"_IP0ND","my-xl-7":"_7BzpU","py-xl-7":"_BIUqI","my-xl-8":"_mZ-p0","py-xl-8":"_Sdn5j","my-xl-9":"_P3V1N","py-xl-9":"_Z9lbF","my-xl-10":"_VAfcF","py-xl-10":"_X4Bq6","background-shine":"_361tW","path":"_qtb88","line":"_XQ9Yw","skeleton":"_BQ0X7","skeleton-animate":"_vEv3B","skeleton-dark":"_l5qsf","skeleton-dark-animate":"_yk159","skeleton-transparent":"_857E4","skeleton-transparent-animate":"_RzhIE","semi-transparent":"_LacS6","no-margins":"_bmB3S","dots":"_5kMd-","colored":"_-lTGD","small":"_GTU1q","extra-small":"_BEmi7","align-left":"_HuG6N","align-right":"_U3h5U"};
|
|
1158
1162
|
|
|
1159
1163
|
/**
|
|
1160
1164
|
* LoadingDots Component - Displays a loading animation with dots.
|
|
@@ -1194,7 +1198,7 @@ LoadingDots.propTypes = {
|
|
|
1194
1198
|
/**
|
|
1195
1199
|
* Button component - A versatile and customizable button for React applications.
|
|
1196
1200
|
* It supports various sizes, styles, and functionalities, including loaders, icons, and handling of click events.
|
|
1197
|
-
* This component can also be used as a link
|
|
1201
|
+
* This component can also be used as a link if "to" is provided.
|
|
1198
1202
|
*
|
|
1199
1203
|
* @component
|
|
1200
1204
|
* @param {Object} props
|
|
@@ -1264,7 +1268,10 @@ const Button = ({
|
|
|
1264
1268
|
setIsCheckShown(false);
|
|
1265
1269
|
}, 2000);
|
|
1266
1270
|
}
|
|
1267
|
-
e
|
|
1271
|
+
if (e != null && e.persist) {
|
|
1272
|
+
// Persisting React's SyntheticEvent to be able to use it in any async context
|
|
1273
|
+
e.persist();
|
|
1274
|
+
}
|
|
1268
1275
|
!_propagatePrimaryButtonClick && e.stopPropagation();
|
|
1269
1276
|
if (_loader) {
|
|
1270
1277
|
setIsLoading(true);
|
|
@@ -1277,11 +1284,11 @@ const Button = ({
|
|
|
1277
1284
|
return /*#__PURE__*/React.createElement(React.Fragment, null, _isFormSubmittingButton ? /*#__PURE__*/React.createElement("input", {
|
|
1278
1285
|
type: "submit",
|
|
1279
1286
|
hidden: true
|
|
1280
|
-
}) : null, _to ? /*#__PURE__*/React.createElement(
|
|
1287
|
+
}) : null, _to ? /*#__PURE__*/React.createElement("a", {
|
|
1281
1288
|
className: classNames,
|
|
1282
1289
|
onClick: e => _handleError(buttonClick, e),
|
|
1283
|
-
|
|
1284
|
-
|
|
1290
|
+
href: _to,
|
|
1291
|
+
ref: buttonRef
|
|
1285
1292
|
}, _icon ? /*#__PURE__*/React.createElement("div", {
|
|
1286
1293
|
className: styles$1["button-with-icon"] + (_bigIcon ? ` ${styles$1["big-icon"]}` : "")
|
|
1287
1294
|
}, /*#__PURE__*/React.createElement("img", {
|
|
@@ -1313,7 +1320,7 @@ const Button = ({
|
|
|
1313
1320
|
Button.propTypes = {
|
|
1314
1321
|
size: PropTypes.oneOf(["xl", "lg", "md", "sm"]),
|
|
1315
1322
|
className: PropTypes.string,
|
|
1316
|
-
mode: PropTypes.oneOf(["transparent", "white", "primary", "primary-bordered", "primary-transparent", "transparent-bordered", "transparent-without-shadow"]),
|
|
1323
|
+
mode: PropTypes.oneOf(["transparent", "white", "white-flat", "primary", "primary-bordered", "primary-transparent", "transparent-bordered", "transparent-without-shadow"]),
|
|
1317
1324
|
onClick: PropTypes.func,
|
|
1318
1325
|
loader: PropTypes.bool,
|
|
1319
1326
|
loading: PropTypes.bool,
|
|
@@ -1353,5 +1360,3753 @@ Button.defaultProps = {
|
|
|
1353
1360
|
handleError: func => func()
|
|
1354
1361
|
};
|
|
1355
1362
|
|
|
1356
|
-
|
|
1363
|
+
const SupportChat = ({
|
|
1364
|
+
url,
|
|
1365
|
+
websiteToken,
|
|
1366
|
+
welcomeMessage: _welcomeMessage = "",
|
|
1367
|
+
locale: _locale = "en"
|
|
1368
|
+
}) => {
|
|
1369
|
+
useEffect(() => {
|
|
1370
|
+
window.chatwootSettings = {
|
|
1371
|
+
position: "right",
|
|
1372
|
+
type: "standard",
|
|
1373
|
+
launcherTitle: _welcomeMessage,
|
|
1374
|
+
locale: _locale
|
|
1375
|
+
};
|
|
1376
|
+
(function (d, t) {
|
|
1377
|
+
var BASE_URL = url;
|
|
1378
|
+
var g = d.createElement(t),
|
|
1379
|
+
s = d.getElementsByTagName(t)[0];
|
|
1380
|
+
g.src = BASE_URL + "/packs/js/sdk.js";
|
|
1381
|
+
g.defer = true;
|
|
1382
|
+
g.async = true;
|
|
1383
|
+
s.parentNode.insertBefore(g, s);
|
|
1384
|
+
g.onload = function () {
|
|
1385
|
+
window.chatwootSDK.run({
|
|
1386
|
+
websiteToken: websiteToken,
|
|
1387
|
+
baseUrl: BASE_URL
|
|
1388
|
+
});
|
|
1389
|
+
};
|
|
1390
|
+
})(document, "script");
|
|
1391
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1392
|
+
}, []);
|
|
1393
|
+
return null;
|
|
1394
|
+
};
|
|
1395
|
+
SupportChat.propTypes = {
|
|
1396
|
+
url: PropTypes.string.isRequired,
|
|
1397
|
+
websiteToken: PropTypes.string.isRequired,
|
|
1398
|
+
welcomeMessage: PropTypes.string,
|
|
1399
|
+
locale: PropTypes.string
|
|
1400
|
+
};
|
|
1401
|
+
SupportChat.defaultProps = {
|
|
1402
|
+
welcomeMessage: "",
|
|
1403
|
+
locale: "en"
|
|
1404
|
+
};
|
|
1405
|
+
|
|
1406
|
+
var s = {"container":"_DYS-g","m-0":"_85R9d","p-0":"_wi0-U","m-1":"_YoewO","p-1":"_vuJBW","m-2":"_ruaYI","p-2":"_dO-cg","m-3":"_3Oh59","p-3":"_x4uSr","m-4":"_zN8us","p-4":"_rjv4I","m-5":"_5T1F-","p-5":"_-nWHT","m-6":"_Skgdf","p-6":"_Oux4z","m-7":"_yKPSq","p-7":"_sOsqF","m-8":"_8EoDT","p-8":"_G4M7b","m-9":"_ZZJBC","p-9":"_2PU3N","m-10":"_XRmXO","p-10":"_lhLN8","ml-0":"_SIynD","pl-0":"_VCLgc","ml-1":"_9vhMT","pl-1":"_4O5ig","ml-2":"_f1saV","pl-2":"_sHxk9","ml-3":"_kiltH","pl-3":"_jUw5Z","ml-4":"_CF1wj","pl-4":"_eXCZ1","ml-5":"_HZlH9","pl-5":"_6xbwv","ml-6":"_jNpyz","pl-6":"_k5lHk","ml-7":"_LgB9F","pl-7":"_ol9Tb","ml-8":"_I25tR","pl-8":"_TN4dc","ml-9":"_ywWxC","pl-9":"_4UsSQ","ml-10":"_9rfct","pl-10":"_LoViP","mr-0":"_-1PzO","pr-0":"_Ekco3","mr-1":"_EfRd0","pr-1":"_GspC-","mr-2":"_91QSK","pr-2":"_dU7Vb","mr-3":"_tW92i","pr-3":"_qNnnG","mr-4":"_XC1FW","pr-4":"_09RVw","mr-5":"_I4KRh","pr-5":"_eag2b","mr-6":"_E-KDc","pr-6":"_0d7JN","mr-7":"_9GUd-","pr-7":"_5Jseh","mr-8":"_Z-z3u","pr-8":"_rjPRa","mr-9":"_R2d68","pr-9":"_YI-lc","mr-10":"_ORX-B","pr-10":"_F5ib8","mt-0":"_OAl4b","pt-0":"_iJ5QU","mt-1":"_x72-c","pt-1":"_DGuKS","mt-2":"_ZYCC4","pt-2":"_6oj3E","mt-3":"_ADXHX","pt-3":"_asJoO","mt-4":"_Bz8Hu","pt-4":"_N7CCb","mt-5":"_OyQQ8","pt-5":"_C46Cf","mt-6":"_Bfrcj","pt-6":"_NN4sF","mt-7":"_RWFV0","pt-7":"_zUauA","mt-8":"_v7txm","pt-8":"_qgvMl","mt-9":"_9s32w","pt-9":"_aJZt7","mt-10":"_feBhr","pt-10":"_EfI4E","mb-0":"_RUzt9","pb-0":"_0Zjn8","mb-1":"_EmcPj","pb-1":"_zuqTv","mb-2":"_jYcdG","pb-2":"_fjMa6","mb-3":"_Lcgzu","pb-3":"_MHHa3","mb-4":"_Aoo0-","pb-4":"_B1ap3","mb-5":"_xdjCc","pb-5":"_lnrC3","mb-6":"_TFmkS","pb-6":"_YNj2R","mb-7":"_i0-JZ","pb-7":"_qSvSL","mb-8":"_7ziqB","pb-8":"_W-Cdi","mb-9":"_lUUVq","pb-9":"_LrgoL","mb-10":"_xX8mu","pb-10":"_DU6C6","mx-0":"_71D1Q","px-0":"_U8dyv","mx-1":"_2VhDN","px-1":"_d8Tlo","mx-2":"_uDkn4","px-2":"_a6Z6h","mx-3":"_W9yqm","px-3":"_MCbca","mx-4":"_sIDVk","px-4":"_wZ8Qz","mx-5":"_-6tEi","px-5":"_8vfpL","mx-6":"_mbXa9","px-6":"_LcSh5","mx-7":"_mF9-c","px-7":"_TfjhQ","mx-8":"_RG7wu","px-8":"_19Sdb","mx-9":"_JkKgx","px-9":"_Ir3LH","mx-10":"_70tWA","px-10":"_d1BrW","my-0":"_0PauS","py-0":"_FP0FE","my-1":"_znn6s","py-1":"_-FHoU","my-2":"_EqZco","py-2":"_fuTfe","my-3":"_D2LQ5","py-3":"_YWhnK","my-4":"_kCXTL","py-4":"_j1vj6","my-5":"_naEoR","py-5":"_QK3Z-","my-6":"_L1TOy","py-6":"_dQH-e","my-7":"_sVZ3Y","py-7":"_Fi779","my-8":"_1mAkX","py-8":"_yTTVv","my-9":"_jCeMV","py-9":"_qfn2C","my-10":"_eqneu","py-10":"_Atu-j","m-sm-0":"_hsKWE","p-sm-0":"_j3m-2","m-sm-1":"_NQOee","p-sm-1":"_sB5mJ","m-sm-2":"_KivoH","p-sm-2":"_YjE2j","m-sm-3":"_kObhZ","p-sm-3":"_TDlKW","m-sm-4":"_aJWH3","p-sm-4":"_Vntgp","m-sm-5":"_k-Hjs","p-sm-5":"_mJt7k","m-sm-6":"_Yxxob","p-sm-6":"_E5a-A","m-sm-7":"_M1OJO","p-sm-7":"_-5OJr","m-sm-8":"_UYrWD","p-sm-8":"_FJeV6","m-sm-9":"_DosWW","p-sm-9":"_T9yES","m-sm-10":"_tLYqH","p-sm-10":"_5dbE7","ml-sm-0":"_PM9wP","pl-sm-0":"_0Eoi7","ml-sm-1":"_Mb-hF","pl-sm-1":"_n4rCF","ml-sm-2":"_JJgiX","pl-sm-2":"_WOdAR","ml-sm-3":"_umhPB","pl-sm-3":"_tETqx","ml-sm-4":"_PQ088","pl-sm-4":"_ON2pV","ml-sm-5":"_IrMTT","pl-sm-5":"_12IZU","ml-sm-6":"_s8vHJ","pl-sm-6":"_9Od-S","ml-sm-7":"_4uuhE","pl-sm-7":"_TTrnJ","ml-sm-8":"_6TEO5","pl-sm-8":"_ZEPSH","ml-sm-9":"_I05iS","pl-sm-9":"_F5rHi","ml-sm-10":"_AJZ2R","pl-sm-10":"_zgudu","mr-sm-0":"_AD7v0","pr-sm-0":"_katlS","mr-sm-1":"_RMNwS","pr-sm-1":"_SC0VS","mr-sm-2":"_YFbee","pr-sm-2":"_I2cqm","mr-sm-3":"_KJzfk","pr-sm-3":"_aDNge","mr-sm-4":"_ERg9u","pr-sm-4":"_wlgHy","mr-sm-5":"_spxY0","pr-sm-5":"_pAEEu","mr-sm-6":"_HM7Z6","pr-sm-6":"_rf9My","mr-sm-7":"_b-V6W","pr-sm-7":"_YFXWV","mr-sm-8":"_96NiE","pr-sm-8":"_0ynKz","mr-sm-9":"_9NqL0","pr-sm-9":"_ZEN2v","mr-sm-10":"_oxmC7","pr-sm-10":"_AsAlz","mt-sm-0":"_Yl8e7","pt-sm-0":"_fkSmd","mt-sm-1":"_l4X69","pt-sm-1":"_i7WdP","mt-sm-2":"_ggYWK","pt-sm-2":"_3-z2g","mt-sm-3":"_IzZ-y","pt-sm-3":"_c3PEq","mt-sm-4":"_BbTvo","pt-sm-4":"_iAXVy","mt-sm-5":"_RNscW","pt-sm-5":"_7dHkj","mt-sm-6":"_iKCRS","pt-sm-6":"_an673","mt-sm-7":"_1Atcr","pt-sm-7":"_KA-HC","mt-sm-8":"_45ifh","pt-sm-8":"_26wBi","mt-sm-9":"_VaqUk","pt-sm-9":"_cx10Q","mt-sm-10":"_83RI-","pt-sm-10":"_vTZq7","mb-sm-0":"_IDhal","pb-sm-0":"_3IjzN","mb-sm-1":"_sjxoK","pb-sm-1":"_fmWwG","mb-sm-2":"_GNlxw","pb-sm-2":"_r3zJV","mb-sm-3":"_FFWRt","pb-sm-3":"_arvvr","mb-sm-4":"_qaIKp","pb-sm-4":"_wSca9","mb-sm-5":"_-cXAZ","pb-sm-5":"_grIUD","mb-sm-6":"_PUPyR","pb-sm-6":"_nA8VL","mb-sm-7":"_t7JYD","pb-sm-7":"_OpbJn","mb-sm-8":"_x1wdN","pb-sm-8":"_3Z-mA","mb-sm-9":"_BPrSY","pb-sm-9":"_b-NQ7","mb-sm-10":"_lSN9Q","pb-sm-10":"_E7xwy","mx-sm-0":"_-cSXk","px-sm-0":"_VlISI","mx-sm-1":"_6derN","px-sm-1":"_in01f","mx-sm-2":"_nskhZ","px-sm-2":"_Thnqg","mx-sm-3":"_xR8rV","px-sm-3":"_zUOaf","mx-sm-4":"_0c-bw","px-sm-4":"_X-g0R","mx-sm-5":"_p3iVv","px-sm-5":"_RdgSR","mx-sm-6":"_vK8xL","px-sm-6":"_xGpLP","mx-sm-7":"_uvb2q","px-sm-7":"_YvDjG","mx-sm-8":"_8nvEy","px-sm-8":"_L3gRg","mx-sm-9":"_pRh4Y","px-sm-9":"_5yig0","mx-sm-10":"_syXdz","px-sm-10":"_TfV9e","my-sm-0":"_WXfoP","py-sm-0":"_SBx-N","my-sm-1":"_dPEq6","py-sm-1":"_UrxQl","my-sm-2":"_ULvTZ","py-sm-2":"_I4Sll","my-sm-3":"_YVvfW","py-sm-3":"_9qflx","my-sm-4":"_j4ltv","py-sm-4":"_u-NN2","my-sm-5":"_mC1LQ","py-sm-5":"_6Wcc2","my-sm-6":"_eE8rw","py-sm-6":"_AeFpW","my-sm-7":"_CAnzA","py-sm-7":"_7h-Vs","my-sm-8":"_Z9Aft","py-sm-8":"_Ww8LU","my-sm-9":"_3sbBd","py-sm-9":"_ZcdCh","my-sm-10":"_PGBDh","py-sm-10":"_i6Fc2","m-lg-0":"_PkOb8","p-lg-0":"_3UpkE","m-lg-1":"_nCbz9","p-lg-1":"_1lThO","m-lg-2":"_ARtYq","p-lg-2":"_vHeBX","m-lg-3":"_cpKBp","p-lg-3":"_n-7p1","m-lg-4":"_1bWgj","p-lg-4":"_niPSW","m-lg-5":"_T9s09","p-lg-5":"_OS1Yy","m-lg-6":"_GvZeA","p-lg-6":"_8cgsA","m-lg-7":"_d0oC7","p-lg-7":"_TuBYJ","m-lg-8":"_slAc2","p-lg-8":"_ay3FH","m-lg-9":"_r3-kR","p-lg-9":"_IuC-U","m-lg-10":"_9P6I9","p-lg-10":"_RUR7-","ml-lg-0":"_xf3wx","pl-lg-0":"_Q-8ZY","ml-lg-1":"_4tg9Q","pl-lg-1":"_2rsuI","ml-lg-2":"_-YU8G","pl-lg-2":"_Eds1N","ml-lg-3":"_tD4RQ","pl-lg-3":"_R0Rk2","ml-lg-4":"_pZcNJ","pl-lg-4":"_5jvIR","ml-lg-5":"_sjk-4","pl-lg-5":"_RwLHv","ml-lg-6":"_dyXyT","pl-lg-6":"_nVLAC","ml-lg-7":"_FPuf8","pl-lg-7":"_M5bYC","ml-lg-8":"_Uet9k","pl-lg-8":"_UrAQv","ml-lg-9":"_VPraG","pl-lg-9":"_8HJkV","ml-lg-10":"_ILHbQ","pl-lg-10":"_QaxaT","mr-lg-0":"_2QrJI","pr-lg-0":"_0XJPD","mr-lg-1":"_qSATY","pr-lg-1":"_fzyzB","mr-lg-2":"_pY4IE","pr-lg-2":"_lc7Gu","mr-lg-3":"_KeRuW","pr-lg-3":"_zc2cu","mr-lg-4":"_RvxVY","pr-lg-4":"_v5XDn","mr-lg-5":"_Kipoo","pr-lg-5":"_K-vHR","mr-lg-6":"_1nl-K","pr-lg-6":"_nVUH-","mr-lg-7":"_M1Ue1","pr-lg-7":"_B75ua","mr-lg-8":"_CmlKz","pr-lg-8":"_FnY12","mr-lg-9":"_8-uRA","pr-lg-9":"_1DG22","mr-lg-10":"_vzMQh","pr-lg-10":"_2iqul","mt-lg-0":"_1eCp-","pt-lg-0":"_ucRUw","mt-lg-1":"_9YQ4e","pt-lg-1":"_pzmV0","mt-lg-2":"_1Z7OF","pt-lg-2":"_FUFhX","mt-lg-3":"_HPI-W","pt-lg-3":"_NwVgV","mt-lg-4":"_duZYq","pt-lg-4":"_h0NfP","mt-lg-5":"_sszGU","pt-lg-5":"_nNi-i","mt-lg-6":"_YYWl4","pt-lg-6":"_rqDrl","mt-lg-7":"_MmkeY","pt-lg-7":"_TZx0T","mt-lg-8":"_mdoth","pt-lg-8":"_2pGPR","mt-lg-9":"_csumg","pt-lg-9":"_BIWSR","mt-lg-10":"_TN07y","pt-lg-10":"_ojQGh","mb-lg-0":"_eXETo","pb-lg-0":"_FeL5F","mb-lg-1":"_Zc1bw","pb-lg-1":"_34sNS","mb-lg-2":"_2XgD-","pb-lg-2":"_OTB4o","mb-lg-3":"_xJ4EC","pb-lg-3":"_-Uc7P","mb-lg-4":"_thqbO","pb-lg-4":"_lqHuw","mb-lg-5":"_gDmAe","pb-lg-5":"_zAiLu","mb-lg-6":"_96HP2","pb-lg-6":"_TCehU","mb-lg-7":"_1Guas","pb-lg-7":"_vn7-Z","mb-lg-8":"_Cc-m8","pb-lg-8":"_dbCuX","mb-lg-9":"_U5Qon","pb-lg-9":"_b54cM","mb-lg-10":"_cGYyA","pb-lg-10":"_U5qus","mx-lg-0":"_TDPp0","px-lg-0":"_hUlWn","mx-lg-1":"_uh5DO","px-lg-1":"_N8r0e","mx-lg-2":"_vsfWJ","px-lg-2":"_LaBP6","mx-lg-3":"_JxquV","px-lg-3":"_Tb8mk","mx-lg-4":"_wDKd1","px-lg-4":"_lIguL","mx-lg-5":"_RSd-x","px-lg-5":"_Mdf1f","mx-lg-6":"_uvK2D","px-lg-6":"_4k20p","mx-lg-7":"_iXTh7","px-lg-7":"_W2faz","mx-lg-8":"_HHM9t","px-lg-8":"_n54Rq","mx-lg-9":"_douwI","px-lg-9":"_KWSHJ","mx-lg-10":"_L-r4v","px-lg-10":"_qeSLU","my-lg-0":"_U9qcF","py-lg-0":"_sSD4U","my-lg-1":"_vNl2c","py-lg-1":"_Cfkag","my-lg-2":"_gVDuB","py-lg-2":"_FwQsB","my-lg-3":"_oiJaa","py-lg-3":"_FASGS","my-lg-4":"_va2Tm","py-lg-4":"_EPTkJ","my-lg-5":"_jvYOP","py-lg-5":"_NTxiG","my-lg-6":"_8Y7Xj","py-lg-6":"_Nrsd7","my-lg-7":"_-e9e5","py-lg-7":"_NOh2l","my-lg-8":"_nyCDl","py-lg-8":"_fpL5p","my-lg-9":"_BbcqH","py-lg-9":"_VlI3L","my-lg-10":"_OGNmd","py-lg-10":"_Q-3bw","m-md-0":"_P6WIc","p-md-0":"_3YuAI","m-md-1":"_VwK3z","p-md-1":"_ArFsR","m-md-2":"_5Hp7o","p-md-2":"_-R4rJ","m-md-3":"_J55VC","p-md-3":"_TxlAj","m-md-4":"_FHPQG","p-md-4":"_iZW10","m-md-5":"_Kiuiv","p-md-5":"_0Qiry","m-md-6":"_Ja9dp","p-md-6":"_GBAS-","m-md-7":"_uuE4u","p-md-7":"_kAuu-","m-md-8":"_CaU4W","p-md-8":"_GbyMJ","m-md-9":"_brWmN","p-md-9":"_-1z0O","m-md-10":"_dIML4","p-md-10":"_eJliD","ml-md-0":"_nuVMQ","pl-md-0":"_PCyiw","ml-md-1":"_NX2qp","pl-md-1":"_yJ39l","ml-md-2":"_BV1WB","pl-md-2":"_x24YX","ml-md-3":"_kvTBw","pl-md-3":"_7G7wo","ml-md-4":"_s1oaO","pl-md-4":"_nTHBq","ml-md-5":"_2AyzN","pl-md-5":"_VmocP","ml-md-6":"_QEhdf","pl-md-6":"_Ywy9t","ml-md-7":"_Mybnc","pl-md-7":"_zHudI","ml-md-8":"_AT4F-","pl-md-8":"_1-CLq","ml-md-9":"_cZ0UL","pl-md-9":"_AKT8q","ml-md-10":"_uo54H","pl-md-10":"_2Edrz","mr-md-0":"_8NtcB","pr-md-0":"_BN5kR","mr-md-1":"_hIIXR","pr-md-1":"_KWi0C","mr-md-2":"_kP9GD","pr-md-2":"_fo1s0","mr-md-3":"_ptgO9","pr-md-3":"_AIGLm","mr-md-4":"_GJNi6","pr-md-4":"_-ZfQA","mr-md-5":"_hW-OG","pr-md-5":"_ePqmc","mr-md-6":"_ghuDl","pr-md-6":"_C9rlZ","mr-md-7":"_bIHB6","pr-md-7":"_xPHzJ","mr-md-8":"_6ZssD","pr-md-8":"_-N-QR","mr-md-9":"_T-XzG","pr-md-9":"_k8hdA","mr-md-10":"_26xvU","pr-md-10":"_U6Fpn","mt-md-0":"_ZIFgT","pt-md-0":"_6JBhp","mt-md-1":"_lIu84","pt-md-1":"_yPyiK","mt-md-2":"_sWcbm","pt-md-2":"_-7S2K","mt-md-3":"_-Zt6X","pt-md-3":"_pmFds","mt-md-4":"_dlz9t","pt-md-4":"_HQv-M","mt-md-5":"_9tUBj","pt-md-5":"_Jdc38","mt-md-6":"_BEZDD","pt-md-6":"_c3r6r","mt-md-7":"_KSCEr","pt-md-7":"_-Tpjo","mt-md-8":"_O-cOs","pt-md-8":"_DPwEz","mt-md-9":"_eBVAf","pt-md-9":"_Fnftl","mt-md-10":"_tlAu5","pt-md-10":"_ufMho","mb-md-0":"_NKFtN","pb-md-0":"_QEyOV","mb-md-1":"_t8EVs","pb-md-1":"_hVqy6","mb-md-2":"_Dwa5p","pb-md-2":"_A6iqX","mb-md-3":"_DAzrF","pb-md-3":"_o9L2-","mb-md-4":"_zMG5y","pb-md-4":"_wC2az","mb-md-5":"_LYF0o","pb-md-5":"_rrbij","mb-md-6":"_tUQcN","pb-md-6":"_MgAD-","mb-md-7":"_9lVsl","pb-md-7":"_Z7z8R","mb-md-8":"_CMrj4","pb-md-8":"_X4Qb0","mb-md-9":"_dQuWg","pb-md-9":"_98aa5","mb-md-10":"_tjfzH","pb-md-10":"_n5Id2","mx-md-0":"_kbQ3E","px-md-0":"_LWqO4","mx-md-1":"_8dqNC","px-md-1":"_QBFBY","mx-md-2":"_QXy6I","px-md-2":"_vBqGQ","mx-md-3":"_asgRe","px-md-3":"_i67O7","mx-md-4":"_-9eXp","px-md-4":"_ZQIEY","mx-md-5":"_l4FN1","px-md-5":"_vYBmq","mx-md-6":"_j4Fiy","px-md-6":"_BLvHs","mx-md-7":"_hg5ZA","px-md-7":"_OclOz","mx-md-8":"_NNMXz","px-md-8":"_KUBjL","mx-md-9":"_x5vxD","px-md-9":"_x1Hj-","mx-md-10":"_RnJFW","px-md-10":"_laoTv","my-md-0":"_gAG5i","py-md-0":"_KQkB8","my-md-1":"_90Emt","py-md-1":"_a93vq","my-md-2":"_wzerE","py-md-2":"_vpawj","my-md-3":"_ijUtT","py-md-3":"_9G-7C","my-md-4":"_GfQTx","py-md-4":"_vGRDM","my-md-5":"_MVaN3","py-md-5":"_5Ju4e","my-md-6":"_StvD2","py-md-6":"_03iO9","my-md-7":"_nlUi8","py-md-7":"_e58lN","my-md-8":"_VKoKR","py-md-8":"_Xa5lD","my-md-9":"_nLjef","py-md-9":"_88ryZ","my-md-10":"_-f4Oc","py-md-10":"_60CJ8","m-xl-0":"_YdUVc","p-xl-0":"_ceNl-","m-xl-1":"_cn-PK","p-xl-1":"_8pBVr","m-xl-2":"_UNu8h","p-xl-2":"_6mIBX","m-xl-3":"_LXA70","p-xl-3":"_nkNjg","m-xl-4":"_DH3uC","p-xl-4":"_b631X","m-xl-5":"_PU5TK","p-xl-5":"_Y9Pj5","m-xl-6":"_DQUfX","p-xl-6":"_cmXs1","m-xl-7":"_Hr4dN","p-xl-7":"_pwyLG","m-xl-8":"_XkBIo","p-xl-8":"_oSfQ1","m-xl-9":"_TXGD8","p-xl-9":"_r8XiA","m-xl-10":"_cbucW","p-xl-10":"_FnUT2","ml-xl-0":"_Lc2b0","pl-xl-0":"_8ofmI","ml-xl-1":"_yOkOl","pl-xl-1":"_5cZix","ml-xl-2":"_F0JQw","pl-xl-2":"_pWIp6","ml-xl-3":"_9CDHy","pl-xl-3":"_M9MOq","ml-xl-4":"_L6LLW","pl-xl-4":"_NZlEo","ml-xl-5":"_Milvj","pl-xl-5":"_N-hPa","ml-xl-6":"_HEU-c","pl-xl-6":"_x-uz3","ml-xl-7":"_xVYoD","pl-xl-7":"_TV3yu","ml-xl-8":"_WVmm8","pl-xl-8":"_Wsfh3","ml-xl-9":"_Rse0C","pl-xl-9":"_p-0My","ml-xl-10":"_LN1Pv","pl-xl-10":"_WtXyv","mr-xl-0":"_aQ8fC","pr-xl-0":"_x7HY1","mr-xl-1":"_rzUKT","pr-xl-1":"_ggRzA","mr-xl-2":"_j88Jb","pr-xl-2":"_6Ztzi","mr-xl-3":"_RNldP","pr-xl-3":"_uC4PP","mr-xl-4":"_14KPU","pr-xl-4":"_RoGxi","mr-xl-5":"_MYSFB","pr-xl-5":"_0fjNm","mr-xl-6":"_aK553","pr-xl-6":"_B-5Vf","mr-xl-7":"_nLta-","pr-xl-7":"_AhsZ-","mr-xl-8":"_emyPo","pr-xl-8":"_Dc6lK","mr-xl-9":"_MqsWc","pr-xl-9":"_j4A-H","mr-xl-10":"_v7rIi","pr-xl-10":"_aIhOc","mt-xl-0":"_1KMHA","pt-xl-0":"_SmBQC","mt-xl-1":"_FpHTe","pt-xl-1":"_tRIkV","mt-xl-2":"_ISAHh","pt-xl-2":"_KcOk8","mt-xl-3":"_cklRo","pt-xl-3":"_MqQSr","mt-xl-4":"_iaWfp","pt-xl-4":"_mcXBl","mt-xl-5":"_qNaL5","pt-xl-5":"_3tRQZ","mt-xl-6":"_MjyPP","pt-xl-6":"_wGTYM","mt-xl-7":"_WkpWd","pt-xl-7":"_-fXMT","mt-xl-8":"_xgRm6","pt-xl-8":"_mNE0F","mt-xl-9":"_tHRgU","pt-xl-9":"_pUanr","mt-xl-10":"_75l23","pt-xl-10":"_VjjB-","mb-xl-0":"_wNZHn","pb-xl-0":"_6weX6","mb-xl-1":"_xbDsG","pb-xl-1":"_WLHB6","mb-xl-2":"_JCck5","pb-xl-2":"_Ef822","mb-xl-3":"_-Izvj","pb-xl-3":"_kizTx","mb-xl-4":"_wDLLt","pb-xl-4":"_6vfXW","mb-xl-5":"_qa8hX","pb-xl-5":"_2cdIL","mb-xl-6":"_PPuX7","pb-xl-6":"_SYsiu","mb-xl-7":"_au29R","pb-xl-7":"_Wo-v3","mb-xl-8":"_7DCh1","pb-xl-8":"_8Qtxz","mb-xl-9":"_U5ePL","pb-xl-9":"_QdSRg","mb-xl-10":"_xwDK-","pb-xl-10":"_5HXQF","mx-xl-0":"_U-5An","px-xl-0":"_t0GQI","mx-xl-1":"_l2Q9y","px-xl-1":"_-6SLy","mx-xl-2":"_OO5ra","px-xl-2":"_DpWnU","mx-xl-3":"_umWwt","px-xl-3":"_gLeFm","mx-xl-4":"_O6nxp","px-xl-4":"_RXch7","mx-xl-5":"_GLCbl","px-xl-5":"_wOP1U","mx-xl-6":"_n1gJF","px-xl-6":"_jHA8-","mx-xl-7":"_ZG5zQ","px-xl-7":"_33-Bi","mx-xl-8":"_wGBwu","px-xl-8":"_jnqCE","mx-xl-9":"_-q541","px-xl-9":"_hQqb-","mx-xl-10":"_oOjVu","px-xl-10":"_8iBI4","my-xl-0":"_5V5-G","py-xl-0":"_ZIETe","my-xl-1":"_VhBbg","py-xl-1":"_qkd1g","my-xl-2":"_MONRd","py-xl-2":"_wBVYq","my-xl-3":"_oUD7x","py-xl-3":"_9C1YA","my-xl-4":"_akz7W","py-xl-4":"_sczsh","my-xl-5":"_CEcRg","py-xl-5":"_xFnw3","my-xl-6":"_RReJQ","py-xl-6":"_gx-E-","my-xl-7":"_NTw09","py-xl-7":"_VniPR","my-xl-8":"_zhVnN","py-xl-8":"_e-lj8","my-xl-9":"_p5Z-c","py-xl-9":"_L1zCc","my-xl-10":"_NqJ-Z","py-xl-10":"_LGmu1","background-shine":"_HkIXJ","path":"_VW1Ys","line":"_x1Ry-","skeleton":"_rh5J1","skeleton-animate":"_ukRhW","skeleton-dark":"_SuY0j","skeleton-dark-animate":"_wHz7j","skeleton-transparent":"_DehL2","skeleton-transparent-animate":"_nwZzB","semi-transparent":"_KJuSy","asset-icon":"_V0pcT","small":"_AXy-6","asset-icon-primary":"_wDjNx","asset-icon-secondary":"_uTjfo"};
|
|
1407
|
+
|
|
1408
|
+
const AssetIcon = ({
|
|
1409
|
+
assetIconSrc,
|
|
1410
|
+
assetIconProtocolSrc: _assetIconProtocolSrc = null,
|
|
1411
|
+
fallbackSrc: _fallbackSrc = null,
|
|
1412
|
+
small: _small = false
|
|
1413
|
+
}) => {
|
|
1414
|
+
const handleFailedLoad = e => {
|
|
1415
|
+
e.target.onerror = null;
|
|
1416
|
+
e.target.src = _fallbackSrc;
|
|
1417
|
+
};
|
|
1418
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
1419
|
+
className: s["asset-icon"] + (_small ? " " + s["small"] : "")
|
|
1420
|
+
}, /*#__PURE__*/React.createElement("img", {
|
|
1421
|
+
src: assetIconSrc,
|
|
1422
|
+
className: s["asset-icon-primary"] + (_small ? " " + s["small"] : ""),
|
|
1423
|
+
alt: " ",
|
|
1424
|
+
onError: handleFailedLoad
|
|
1425
|
+
}), _assetIconProtocolSrc ? /*#__PURE__*/React.createElement("img", {
|
|
1426
|
+
src: _assetIconProtocolSrc,
|
|
1427
|
+
className: s["asset-icon-secondary"] + (_small ? " " + s["small"] : ""),
|
|
1428
|
+
alt: " ",
|
|
1429
|
+
onError: handleFailedLoad
|
|
1430
|
+
}) : "");
|
|
1431
|
+
};
|
|
1432
|
+
AssetIcon.propTypes = {
|
|
1433
|
+
assetIconSrc: PropTypes.string.isRequired,
|
|
1434
|
+
assetIconProtocolSrc: PropTypes.string,
|
|
1435
|
+
fallbackSrc: PropTypes.string,
|
|
1436
|
+
small: PropTypes.bool
|
|
1437
|
+
};
|
|
1438
|
+
AssetIcon.defaultProps = {
|
|
1439
|
+
assetIconProtocolSrc: null,
|
|
1440
|
+
fallbackSrc: null,
|
|
1441
|
+
small: false
|
|
1442
|
+
};
|
|
1443
|
+
|
|
1444
|
+
class LogsStorage {
|
|
1445
|
+
static saveLog(log) {
|
|
1446
|
+
this._inMemoryStorage.push(log);
|
|
1447
|
+
}
|
|
1448
|
+
static getInMemoryLogs() {
|
|
1449
|
+
return this._inMemoryStorage;
|
|
1450
|
+
}
|
|
1451
|
+
static getAllLogs() {
|
|
1452
|
+
let storedLogs = "";
|
|
1453
|
+
if (typeof window !== "undefined") {
|
|
1454
|
+
storedLogs = localStorage.getItem(this._logsStorageId);
|
|
1455
|
+
}
|
|
1456
|
+
return `${storedLogs}\n${this._inMemoryStorage.join("\n")}`;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
/**
|
|
1460
|
+
* @param logger {Logger}
|
|
1461
|
+
*/
|
|
1462
|
+
static saveToTheDisk(logger) {
|
|
1463
|
+
try {
|
|
1464
|
+
const MAX_LOCAL_STORAGE_VOLUME_BYTES = 5 * 1024 * 1024;
|
|
1465
|
+
const MAX_LOGS_STORAGE_BYTES = MAX_LOCAL_STORAGE_VOLUME_BYTES * 0.65;
|
|
1466
|
+
if (typeof window !== "undefined") {
|
|
1467
|
+
const existingLogs = localStorage.getItem(this._logsStorageId);
|
|
1468
|
+
const logsString = `${existingLogs}\n${this._inMemoryStorage.join("\n")}`;
|
|
1469
|
+
const lettersCountToRemove = logsString.length - Math.round(MAX_LOGS_STORAGE_BYTES / 2);
|
|
1470
|
+
if (lettersCountToRemove > 0) {
|
|
1471
|
+
localStorage.setItem(this._logsStorageId, logsString.slice(lettersCountToRemove, logsString.length));
|
|
1472
|
+
} else {
|
|
1473
|
+
localStorage.setItem(this._logsStorageId, logsString);
|
|
1474
|
+
}
|
|
1475
|
+
this._inMemoryStorage = [];
|
|
1476
|
+
}
|
|
1477
|
+
} catch (e) {
|
|
1478
|
+
logger == null || logger.logError(e, "saveToTheDisk", "Failed to save logs to disk");
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
static removeAllClientLogs() {
|
|
1482
|
+
if (typeof window !== "undefined") {
|
|
1483
|
+
if (localStorage.getItem("doNotRemoveClientLogsWhenSignedOut") !== "true") {
|
|
1484
|
+
localStorage.removeItem(this._logsStorageId);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
this._inMemoryStorage = [];
|
|
1488
|
+
}
|
|
1489
|
+
static setDoNotRemoveClientLogsWhenSignedOut(value) {
|
|
1490
|
+
if (typeof window !== "undefined") {
|
|
1491
|
+
localStorage.setItem("doNotRemoveClientLogsWhenSignedOut", value);
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
LogsStorage._inMemoryStorage = [];
|
|
1496
|
+
LogsStorage._logsStorageId = "clietnLogs_j203fj2D0n-d1";
|
|
1497
|
+
|
|
1498
|
+
/**
|
|
1499
|
+
* Stringify given object by use of JSON.stringify but handles circular structures and "response", "request" properties
|
|
1500
|
+
* to avoid stringing redundant data when printing errors containing request/response objects.
|
|
1501
|
+
*
|
|
1502
|
+
* @param object - object to be stringed
|
|
1503
|
+
* @param indent - custom indentation
|
|
1504
|
+
* @return {string} - stringed object
|
|
1505
|
+
*/
|
|
1506
|
+
function safeStringify(object, indent = 2) {
|
|
1507
|
+
let cache = [];
|
|
1508
|
+
if (typeof object === "string" || typeof object === "function" || typeof object === "number" || typeof object === "undefined" || typeof object === "boolean") {
|
|
1509
|
+
return String(object);
|
|
1510
|
+
}
|
|
1511
|
+
const retVal = JSON.stringify(object, (key, value) => {
|
|
1512
|
+
if (key.toLowerCase().includes("request")) {
|
|
1513
|
+
return JSON.stringify({
|
|
1514
|
+
body: value == null ? void 0 : value.body,
|
|
1515
|
+
query: value == null ? void 0 : value.query,
|
|
1516
|
+
headers: value == null ? void 0 : value.headers
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
if (key.toLowerCase().includes("response")) {
|
|
1520
|
+
return JSON.stringify({
|
|
1521
|
+
statusText: value == null ? void 0 : value.statusText,
|
|
1522
|
+
status: value == null ? void 0 : value.status,
|
|
1523
|
+
data: value == null ? void 0 : value.data,
|
|
1524
|
+
headers: value == null ? void 0 : value.headers
|
|
1525
|
+
});
|
|
1526
|
+
}
|
|
1527
|
+
return typeof value === "object" && value !== null ? cache.includes(value) ? "duplicated reference" // Duplicated references were found, discarding this key
|
|
1528
|
+
: cache.push(value) && value // Store value in our collection
|
|
1529
|
+
: value;
|
|
1530
|
+
}, indent);
|
|
1531
|
+
cache = null;
|
|
1532
|
+
return retVal;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
class Logger {
|
|
1536
|
+
/**
|
|
1537
|
+
* Logs to client logs storage.
|
|
1538
|
+
*
|
|
1539
|
+
* WARNING! this method should ce used carefully for critical logging as we have the restriction for storing logs
|
|
1540
|
+
* on client side as we store them inside the local storage. Please see details inside storage.js
|
|
1541
|
+
* @param logString {string} log string
|
|
1542
|
+
* @param source {string} source of the log entry
|
|
1543
|
+
*/
|
|
1544
|
+
static log(logString, source) {
|
|
1545
|
+
const timestamp = new Date().toISOString();
|
|
1546
|
+
LogsStorage.saveLog(`${timestamp}|${source}:${logString}`);
|
|
1547
|
+
}
|
|
1548
|
+
static logError(e, settingFunction, additionalMessage = "", onlyToConsole = false) {
|
|
1549
|
+
var _e$errorDescription, _e$howToFix;
|
|
1550
|
+
let message = `\nFunction call ${settingFunction != null ? settingFunction : ""} failed. Error message: ${e == null ? void 0 : e.message}. ${additionalMessage} `;
|
|
1551
|
+
message += `${(_e$errorDescription = e == null ? void 0 : e.errorDescription) != null ? _e$errorDescription : ""}${(_e$howToFix = e == null ? void 0 : e.howToFix) != null ? _e$howToFix : ""}` + ((e == null ? void 0 : e.httpStatus) === 403 ? "Authentication has expired or was lost. " : "");
|
|
1552
|
+
if (e != null && e.response) {
|
|
1553
|
+
try {
|
|
1554
|
+
const responseData = safeStringify({
|
|
1555
|
+
response: e.response
|
|
1556
|
+
});
|
|
1557
|
+
responseData && (message += `\n${responseData}. `);
|
|
1558
|
+
} catch (e) {}
|
|
1559
|
+
}
|
|
1560
|
+
const finalErrorText = message + ". " + safeStringify(e);
|
|
1561
|
+
// eslint-disable-next-line no-console
|
|
1562
|
+
console.error(finalErrorText, e);
|
|
1563
|
+
if (!onlyToConsole) {
|
|
1564
|
+
this.log(finalErrorText, "logError");
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
function useCallHandlingErrors() {
|
|
1570
|
+
const [, setState] = useState();
|
|
1571
|
+
return useCallback(async (functionToBeCalled, event) => {
|
|
1572
|
+
try {
|
|
1573
|
+
await functionToBeCalled(event);
|
|
1574
|
+
} catch (error) {
|
|
1575
|
+
Logger.logError(error, (functionToBeCalled == null ? void 0 : functionToBeCalled.name) || "errorBoundaryTrigger", "Caught by ErrorBoundary");
|
|
1576
|
+
// Triggering ErrorBoundary
|
|
1577
|
+
setState(() => {
|
|
1578
|
+
throw error;
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
}, []);
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
/**
|
|
1585
|
+
* Adds reference to standard state variable. It is helpful to be able to use state variable value inside
|
|
1586
|
+
* event handlers and other callbacks without the need to call setState(prev => { value = prev; return prev; }).
|
|
1587
|
+
*
|
|
1588
|
+
* @param initialValue {any} to be passed to useState
|
|
1589
|
+
* @return {[React.Ref, function]} reference to state variable and its setter
|
|
1590
|
+
*/
|
|
1591
|
+
function useReferredState(initialValue) {
|
|
1592
|
+
const [state, setState] = React.useState(initialValue);
|
|
1593
|
+
const reference = React.useRef(state);
|
|
1594
|
+
const setReferredState = value => {
|
|
1595
|
+
if (value && {}.toString.call(value) === "[object Function]") {
|
|
1596
|
+
value = value(reference.current);
|
|
1597
|
+
}
|
|
1598
|
+
reference.current = value;
|
|
1599
|
+
setState(value);
|
|
1600
|
+
};
|
|
1601
|
+
return [reference, setReferredState];
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
const handleClickOutside = (exceptionsRefs, callback) => {
|
|
1605
|
+
function handleClick(event) {
|
|
1606
|
+
const isExceptionClicked = exceptionsRefs.find(ref => (ref == null ? void 0 : ref.current) && ref.current.contains(event.target));
|
|
1607
|
+
if (!isExceptionClicked) {
|
|
1608
|
+
callback();
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
document.addEventListener("click", handleClick);
|
|
1612
|
+
return () => document.removeEventListener("click", handleClick);
|
|
1613
|
+
};
|
|
1614
|
+
|
|
1615
|
+
const PARAMETER_VALUES_SEPARATOR = "|*|"; // Sting that with high probability will not be in the user's data
|
|
1616
|
+
|
|
1617
|
+
/**
|
|
1618
|
+
* Adds specified parameter with values to the URL query string
|
|
1619
|
+
*
|
|
1620
|
+
* @param parameterName - String - name of the parameter
|
|
1621
|
+
* @param values - Array of String values
|
|
1622
|
+
* @param updateURLCallback - callback that will be called with the updated query string. Can be used to save it to URL
|
|
1623
|
+
*/
|
|
1624
|
+
function saveQueryParameterAndValues(parameterName, values, updateURLCallback = newQueryString => {}) {
|
|
1625
|
+
let parametersAndValues = parseSearchString();
|
|
1626
|
+
parametersAndValues = parametersAndValues.filter(parameterAndValues => parameterAndValues[0] !== parameterName);
|
|
1627
|
+
const parameterValuesForURL = encodeURIComponent(values.join(PARAMETER_VALUES_SEPARATOR));
|
|
1628
|
+
parametersAndValues.push([parameterName, parameterValuesForURL]);
|
|
1629
|
+
const newQueryString = `?${parametersAndValues.map(parameterAndValues => parameterAndValues.join("=")).join("&")}`;
|
|
1630
|
+
updateURLCallback(newQueryString);
|
|
1631
|
+
return newQueryString;
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
/**
|
|
1635
|
+
* Removes specified parameter with values from the URL query string
|
|
1636
|
+
*
|
|
1637
|
+
* @param parameterName - String - name of the parameter
|
|
1638
|
+
* @param updateURLCallback - callback that will be called with the updated query string. Can be used to save it to URL
|
|
1639
|
+
*/
|
|
1640
|
+
// TODO: [tests, moderate] units required the same as or other functions in this module
|
|
1641
|
+
function removeQueryParameterAndValues(parameterName, updateURLCallback = newQueryString => {}) {
|
|
1642
|
+
let parametersAndValues = parseSearchString();
|
|
1643
|
+
parametersAndValues = parametersAndValues.filter(parameterAndValues => parameterAndValues[0] !== parameterName);
|
|
1644
|
+
const newQueryString = `?${parametersAndValues.map(parameterAndValues => parameterAndValues.join("=")).join("&")}`;
|
|
1645
|
+
updateURLCallback(newQueryString);
|
|
1646
|
+
return newQueryString;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
/**
|
|
1650
|
+
* Retrieves parameter values from the URL query string.
|
|
1651
|
+
*
|
|
1652
|
+
* If there are several parameters with the same name in the URL then all their values are returned
|
|
1653
|
+
*
|
|
1654
|
+
* @param name {string} - parameter name
|
|
1655
|
+
* @return {string[]} [] - if the parameter is not present in URL. [""] - if parameter present but has empty value
|
|
1656
|
+
*/
|
|
1657
|
+
function getQueryParameterValues(name) {
|
|
1658
|
+
return parseSearchString().filter(parameterAndValue => parameterAndValue[0] === name).reduce((allValues, parameterAndValue) => {
|
|
1659
|
+
const values = decodeURIComponent(parameterAndValue[1] || "").split(PARAMETER_VALUES_SEPARATOR);
|
|
1660
|
+
return [...allValues, ...values];
|
|
1661
|
+
}, []);
|
|
1662
|
+
}
|
|
1663
|
+
function parseSearchString() {
|
|
1664
|
+
var _window$location$sear;
|
|
1665
|
+
const trimmed = (((_window$location$sear = window.location.search) == null ? void 0 : _window$location$sear.slice(1)) || "").trim();
|
|
1666
|
+
return trimmed && trimmed.split("&").map(parameterAndValue => parameterAndValue.split("=")) || [];
|
|
1667
|
+
}
|
|
1668
|
+
function getQueryParameterSingleValue(name) {
|
|
1669
|
+
return (getQueryParameterValues(name) || [])[0];
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
/**
|
|
1673
|
+
* This function improves the passed error object (its message) by adding the passed function name
|
|
1674
|
+
* and additional message to it.
|
|
1675
|
+
* This is useful as Javascript doesn't guarantee the stack-traces, so we should manually add these details to errors
|
|
1676
|
+
* to be able to troubleshoot.
|
|
1677
|
+
*
|
|
1678
|
+
* @param e {Error}
|
|
1679
|
+
* @param settingFunction {string}
|
|
1680
|
+
* @param [additionalMessage=""] {string|undefined}
|
|
1681
|
+
* @throws {Error} always rethrows the original passed error but with an improved message
|
|
1682
|
+
*/
|
|
1683
|
+
function improveAndRethrow(e, settingFunction, additionalMessage = "") {
|
|
1684
|
+
const message = improvedErrorMessage(e, settingFunction, additionalMessage);
|
|
1685
|
+
if (e) {
|
|
1686
|
+
e.message = message;
|
|
1687
|
+
throw e; // to preserve existing stacktrace if present
|
|
1688
|
+
}
|
|
1689
|
+
throw new Error(message);
|
|
1690
|
+
}
|
|
1691
|
+
function improvedErrorMessage(e, settingFunction, additionalMessage) {
|
|
1692
|
+
let message = `\nFunction call ${settingFunction != null ? settingFunction : ""} failed. `;
|
|
1693
|
+
e && e.message && (message += `Error message: ${e.message}. `);
|
|
1694
|
+
additionalMessage && (message += `${additionalMessage} `);
|
|
1695
|
+
return message;
|
|
1696
|
+
}
|
|
1697
|
+
function logErrorOrOutputToConsole(e) {
|
|
1698
|
+
try {
|
|
1699
|
+
// TODO: [dev] remove this after few weeks of testing output in real life
|
|
1700
|
+
// eslint-disable-next-line no-console
|
|
1701
|
+
console.log("BEFORE SAFE", e);
|
|
1702
|
+
Logger.log("logErrorOrOutputToConsole", safeStringify(e));
|
|
1703
|
+
} catch (e) {
|
|
1704
|
+
// eslint-disable-next-line no-console
|
|
1705
|
+
console.log("logErrorOrOutputToConsole", e);
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
class FiatCurrenciesService {
|
|
1710
|
+
static getFullCurrencyNameByCode(code = "") {
|
|
1711
|
+
const data = fiatCurrenciesList.find(currencyData => currencyData[0] === code.toUpperCase());
|
|
1712
|
+
return data && data[2] || null;
|
|
1713
|
+
}
|
|
1714
|
+
static isCodeValid(code) {
|
|
1715
|
+
return !!fiatCurrenciesList.find(currenciesData => currenciesData[0] === code);
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
/**
|
|
1719
|
+
* Returns currency symbol by code if present
|
|
1720
|
+
*
|
|
1721
|
+
* @param code {string} currency code
|
|
1722
|
+
* @return {string|null} code or null if there is no symbol for the currency
|
|
1723
|
+
*/
|
|
1724
|
+
static getCurrencySymbolByCode(code = "") {
|
|
1725
|
+
var _data$;
|
|
1726
|
+
const data = fiatCurrenciesList.find(currencyData => currencyData[0] === code.toUpperCase());
|
|
1727
|
+
return (_data$ = data == null ? void 0 : data[1]) != null ? _data$ : null;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
/**
|
|
1731
|
+
* @param code {string}
|
|
1732
|
+
* @return {number|null}
|
|
1733
|
+
*/
|
|
1734
|
+
static getCurrencyDecimalCountByCode(code = "") {
|
|
1735
|
+
var _data$2;
|
|
1736
|
+
const data = fiatCurrenciesList.find(currencyData => currencyData[0] === code.toUpperCase());
|
|
1737
|
+
return (_data$2 = data == null ? void 0 : data[3]) != null ? _data$2 : null;
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
const fiatCurrenciesList = [["USD", "$", "US Dollar", 2], ["CAD", "CA$", "Canadian Dollar", 2], ["EUR", "€", "Euro", 2], ["AED", "AED", "UAE Dirham", 2], ["AFN", "؋", "Afghan Afghani", 0], ["ALL", "ALL", "Albanian Lek", 0], ["AMD", "֏", "Armenian Dram", 0], ["ARS", "AR$", "Argentine Peso", 2], ["AUD", "AU$", "Australian Dollar", 2], ["AZN", "₼", "Azerbaijani Manat", 2], ["BAM", "KM", "Bosnia-Herzegovina Convertible Mark", 2], ["BDT", "Tk", "Bangladeshi Taka", 2], ["BGN", "BGN", "Bulgarian Lev", 2], ["BHD", "BD", "Bahraini Dinar", 3], ["BIF", "FBu", "Burundian Franc", 0], ["BND", "BN$", "Brunei Dollar", 2], ["BOB", "Bs", "Bolivian Boliviano", 2], ["BRL", "R$", "Brazilian Real", 2], ["BWP", "BWP", "Botswanan Pula", 2], ["BYN", "Br", "Belarusian Ruble", 2], ["BZD", "BZ$", "Belize Dollar", 2], ["CDF", "CDF", "Congolese Franc", 2], ["CHF", "CHF", "Swiss Franc", 2], ["CLP", "CL$", "Chilean Peso", 0], ["CNY", "CN¥", "Chinese Yuan", 2], ["COP", "CO$", "Colombian Peso", 0], ["CRC", "₡", "Costa Rican Colón", 0], ["CVE", "CV$", "Cape Verdean Escudo", 2], ["CZK", "Kč", "Czech Republic Koruna", 2], ["DJF", "Fdj", "Djiboutian Franc", 0], ["DKK", "Dkr", "Danish Krone", 2], ["DOP", "RD$", "Dominican Peso", 2], ["DZD", "DA", "Algerian Dinar", 2], ["EEK", "Ekr", "Estonian Kroon", 2], ["EGP", "EGP", "Egyptian Pound", 2], ["ERN", "Nfk", "Eritrean Nakfa", 2], ["ETB", "Br", "Ethiopian Birr", 2], ["GBP", "£", "British Pound Sterling", 2], ["GEL", "₾", "Georgian Lari", 2], ["GHS", "₵", "Ghanaian Cedi", 2], ["GNF", "FG", "Guinean Franc", 0], ["GTQ", "GTQ", "Guatemalan Quetzal", 2], ["HKD", "HK$", "Hong Kong Dollar", 2], ["HNL", "HNL", "Honduran Lempira", 2], ["HRK", "kn", "Croatian Kuna", 2], ["HUF", "Ft", "Hungarian Forint", 0], ["IDR", "Rp", "Indonesian Rupiah", 0], ["ILS", "₪", "Israeli New Sheqel", 2], ["INR", "₹", "Indian Rupee", 2], ["IQD", "IQD", "Iraqi Dinar", 0], ["IRR", "﷼", "Iranian Rial", 0], ["ISK", "Ikr", "Icelandic Króna", 0], ["JMD", "J$", "Jamaican Dollar", 2], ["JOD", "JD", "Jordanian Dinar", 3], ["JPY", "¥", "Japanese Yen", 0], ["KES", "Ksh", "Kenyan Shilling", 2], ["KHR", "KHR", "Cambodian Riel", 2], ["KMF", "CF", "Comorian Franc", 0], ["KRW", "₩", "South Korean Won", 0], ["KWD", "KD", "Kuwaiti Dinar", 3], ["KZT", "₸", "Kazakhstani Tenge", 2], ["LBP", "LB£", "Lebanese Pound", 0], ["LKR", "SLRs", "Sri Lankan Rupee", 2], ["LTL", "Lt", "Lithuanian Litas", 2], ["LVL", "Ls", "Latvian Lats", 2], ["LYD", "LD", "Libyan Dinar", 3], ["MAD", "MAD", "Moroccan Dirham", 2], ["MDL", "MDL", "Moldovan Leu", 2], ["MGA", "MGA", "Malagasy Ariary", 0], ["MKD", "MKD", "Macedonian Denar", 2], ["MMK", "MMK", "Myanma Kyat", 0], ["MNT", "₮", "Mongolian Tugrik", 0], ["MOP", "MOP$", "Macanese Pataca", 2], ["MUR", "MURs", "Mauritian Rupee", 0], ["MXN", "MX$", "Mexican Peso", 2], ["MYR", "RM", "Malaysian Ringgit", 2], ["MZN", "MTn", "Mozambican Metical", 2], ["NAD", "N$", "Namibian Dollar", 2], ["NGN", "₦", "Nigerian Naira", 2], ["NIO", "C$", "Nicaraguan Córdoba", 2], ["NOK", "Nkr", "Norwegian Krone", 2], ["NPR", "NPRs", "Nepalese Rupee", 2], ["NZD", "NZ$", "New Zealand Dollar", 2], ["OMR", "OMR", "Omani Rial", 3], ["PAB", "B/.", "Panamanian Balboa", 2], ["PEN", "S/.", "Peruvian Nuevo Sol", 2], ["PHP", "₱", "Philippine Peso", 2], ["PKR", "PKRs", "Pakistani Rupee", 0], ["PLN", "zł", "Polish Zloty", 2], ["PYG", "₲", "Paraguayan Guarani", 0], ["QAR", "QR", "Qatari Rial", 2], ["RON", "RON", "Romanian Leu", 2], ["RSD", "din.", "Serbian Dinar", 0], ["RUB", "₽", "Russian Ruble", 2], ["RWF", "RWF", "Rwandan Franc", 0], ["SAR", "SR", "Saudi Riyal", 2], ["SDG", "SDG", "Sudanese Pound", 2], ["SEK", "Skr", "Swedish Krona", 2], ["SGD", "S$", "Singapore Dollar", 2], ["SOS", "Ssh", "Somali Shilling", 0], ["SYP", "SY£", "Syrian Pound", 0], ["THB", "฿", "Thai Baht", 2], ["TND", "DT", "Tunisian Dinar", 3], ["TOP", "T$", "Tongan Paʻanga", 2], ["TRY", "₺", "Turkish Lira", 2], ["TTD", "TT$", "Trinidad and Tobago Dollar", 2], ["TWD", "NT$", "New Taiwan Dollar", 2], ["TZS", "TSh", "Tanzanian Shilling", 0], ["UAH", "₴", "Ukrainian Hryvnia", 2], ["UGX", "USh", "Ugandan Shilling", 0], ["UYU", "$U", "Uruguayan Peso", 2], ["UZS", "UZS", "Uzbekistan Som", 0], ["VEF", "Bs.F.", "Venezuelan Bolívar", 2], ["VND", "₫", "Vietnamese Dong", 0], ["XAF", "FCFA", "CFA Franc BEAC", 0], ["XOF", "CFA", "CFA Franc BCEAO", 0], ["YER", "﷼", "Yemeni Rial", 0], ["ZAR", "R", "South African Rand", 2], ["ZMK", "ZK", "Zambian Kwacha", 0], ["ZWL", "ZWL$", "Zimbabwean Dollar", 0]];
|
|
1741
|
+
|
|
1742
|
+
function _extends() {
|
|
1743
|
+
_extends = Object.assign ? Object.assign.bind() : function (target) {
|
|
1744
|
+
for (var i = 1; i < arguments.length; i++) {
|
|
1745
|
+
var source = arguments[i];
|
|
1746
|
+
for (var key in source) {
|
|
1747
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
1748
|
+
target[key] = source[key];
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
return target;
|
|
1753
|
+
};
|
|
1754
|
+
return _extends.apply(this, arguments);
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
// TODO: [dev] return addCommasToAmountString internal method to encapsulate commas adding
|
|
1758
|
+
|
|
1759
|
+
class AmountUtils {
|
|
1760
|
+
static fiatXs(amount, code) {
|
|
1761
|
+
return this.fiat(amount, code, {
|
|
1762
|
+
extraSmallLength: true
|
|
1763
|
+
});
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
/**
|
|
1767
|
+
* Universal method for rendering of fiat amounts, taking into account the rules of
|
|
1768
|
+
* the passed fiat currency code.
|
|
1769
|
+
*
|
|
1770
|
+
* TODO: [feature, high] remove 'number' from accepted types list task_id=1e692bcfabbe487a9d1638fc8ff17448
|
|
1771
|
+
* @param amount {BigNumber|number|string|null|undefined} The number value to be trimmed
|
|
1772
|
+
* @param currencyCode {string|null} The currency code. Can be omitted if { ticker: false } in the config
|
|
1773
|
+
* @param [passedParams={}] {object} Formatting parameters
|
|
1774
|
+
* @return {string} Formatted fiat amount string
|
|
1775
|
+
*/
|
|
1776
|
+
static fiat(amount, currencyCode, passedParams = {}) {
|
|
1777
|
+
try {
|
|
1778
|
+
const params = _extends({}, this.defaultFiatParams, passedParams);
|
|
1779
|
+
if (this._checkIfAmountInvalid(amount, true) || typeof currencyCode !== "string") return "NULL";
|
|
1780
|
+
const currencySymbol = FiatCurrenciesService.getCurrencySymbolByCode(currencyCode);
|
|
1781
|
+
const currencyDecimalCount = FiatCurrenciesService.getCurrencyDecimalCountByCode(currencyCode);
|
|
1782
|
+
const trimmedByMaxDigits = BigNumber(amount).toFixed(currencyDecimalCount, BigNumber.ROUND_FLOOR);
|
|
1783
|
+
let processedAmount = BigNumber(trimmedByMaxDigits);
|
|
1784
|
+
if (params.collapsible && processedAmount.gte(BigNumber("1000000"))) {
|
|
1785
|
+
processedAmount = this._collapseToMillionsAndFormat(processedAmount, this.collapsedDecimalCount, params);
|
|
1786
|
+
} else {
|
|
1787
|
+
const limitResult = this._limitTotalAmountLengthIfNeeded(trimmedByMaxDigits, params);
|
|
1788
|
+
processedAmount = BigNumber(limitResult.processedAmount).toFormat(); // Adds commas to integer part
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
// Add the currency code or currency symbol, if symbol is enabled and available
|
|
1792
|
+
if (params.ticker) {
|
|
1793
|
+
if (typeof currencySymbol === "string" && params.enableCurrencySymbols) {
|
|
1794
|
+
processedAmount = currencySymbol + (currencySymbol.length > 1 ? " " : "") + processedAmount;
|
|
1795
|
+
} else {
|
|
1796
|
+
processedAmount = processedAmount + " " + currencyCode;
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
return processedAmount;
|
|
1800
|
+
} catch (e) {
|
|
1801
|
+
improveAndRethrow(e, "fiat", `Passed: ${amount}`);
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
static cryptoWoTicker(amount, digits) {
|
|
1805
|
+
return this.crypto(amount, null, digits, {
|
|
1806
|
+
ticker: false
|
|
1807
|
+
});
|
|
1808
|
+
}
|
|
1809
|
+
static cryptoWoTickerXs(amount, digits) {
|
|
1810
|
+
return this.crypto(amount, null, digits, {
|
|
1811
|
+
ticker: false,
|
|
1812
|
+
extraSmallLength: true
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1815
|
+
static cryptoXs(amount, ticker, digits) {
|
|
1816
|
+
return this.crypto(amount, ticker, digits, {
|
|
1817
|
+
extraSmallLength: true,
|
|
1818
|
+
periods: false
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
static cryptoFull(amount, ticker, digits) {
|
|
1822
|
+
return this.crypto(amount, ticker, digits, {
|
|
1823
|
+
collapsible: false,
|
|
1824
|
+
trim: false,
|
|
1825
|
+
limitTotalLength: false
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
static cryptoFullPureNumber(amount, digits) {
|
|
1829
|
+
return this.crypto(amount, null, digits, {
|
|
1830
|
+
ticker: false,
|
|
1831
|
+
collapsible: false,
|
|
1832
|
+
trim: false,
|
|
1833
|
+
limitTotalLength: false,
|
|
1834
|
+
numberPartsSeparator: false
|
|
1835
|
+
});
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
/**
|
|
1839
|
+
* Universal method for rendering of crypto amounts, taking into account the rules of
|
|
1840
|
+
* the passed ticker. Requires the number of digits after period to be less of equal to
|
|
1841
|
+
* the number of digits, supported by the passed ticker.
|
|
1842
|
+
*
|
|
1843
|
+
* @param amount {BigNumber|string|null|undefined} The number value to be formatted
|
|
1844
|
+
* @param ticker {string|null} Coin ticker
|
|
1845
|
+
* @param [digits=8] {number} max digits after the dot
|
|
1846
|
+
* @param passedParams {object} Formatting parameters
|
|
1847
|
+
* @return {string} Formatted crypto amount string
|
|
1848
|
+
*/
|
|
1849
|
+
static crypto(amount, ticker, digits = this.significantDecimalCount, passedParams) {
|
|
1850
|
+
try {
|
|
1851
|
+
const params = _extends({}, this.defaultCryptoParams, passedParams);
|
|
1852
|
+
if (this._checkIfAmountInvalid(amount) || typeof ticker !== "string" && params.ticker) return "NULL";
|
|
1853
|
+
let addPeriods = false;
|
|
1854
|
+
const amountBigNumber = BigNumber(amount);
|
|
1855
|
+
let processedAmount = amountBigNumber.toFixed(digits, BigNumber.ROUND_FLOOR);
|
|
1856
|
+
processedAmount = this.removeRedundantRightZerosFromNumberString(processedAmount);
|
|
1857
|
+
const originalAmountDecimalPlaces = BigNumber(processedAmount).decimalPlaces();
|
|
1858
|
+
// Check decimal count and throw an error, if the amount has more decimal digits than supported by the asset
|
|
1859
|
+
if (originalAmountDecimalPlaces > digits) {
|
|
1860
|
+
const errorMessage = `An attempt to render a crypto value with too many digits after period was made: ${amount}, allowed digits: ${digits}. This is a no-op, since the logical and visually rendered values would differ, which is not acceptable for crypto amounts. Please trim the amount before rendering, using the trimCryptoAmountByCoin(amount, coin) method.`;
|
|
1861
|
+
// throw new Error(errorMessage);
|
|
1862
|
+
// eslint-disable-next-line no-console
|
|
1863
|
+
console.log(errorMessage, "crypto");
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
// Shortening the value to general significant number of digits after period
|
|
1867
|
+
if (params.trim) {
|
|
1868
|
+
processedAmount = this.removeRedundantRightZerosFromNumberString(amountBigNumber.toFixed(this.significantDecimalCount, BigNumber.ROUND_FLOOR));
|
|
1869
|
+
addPeriods = originalAmountDecimalPlaces > this.significantDecimalCount;
|
|
1870
|
+
}
|
|
1871
|
+
const limitResult = this._limitTotalAmountLengthIfNeeded(processedAmount, params);
|
|
1872
|
+
processedAmount = limitResult.processedAmount;
|
|
1873
|
+
addPeriods || (addPeriods = limitResult.addPeriods);
|
|
1874
|
+
let wereMillionsCollapsed = false;
|
|
1875
|
+
if (params.collapsible && amountBigNumber.gte("1000000")) {
|
|
1876
|
+
// Collapse the 1M+ amounts if applicable
|
|
1877
|
+
processedAmount = this._collapseToMillionsAndFormat(BigNumber(processedAmount), this.collapsedDecimalCount, params);
|
|
1878
|
+
wereMillionsCollapsed = true;
|
|
1879
|
+
} else if (params.numberPartsSeparator) {
|
|
1880
|
+
// Add separators to integer part of the amount
|
|
1881
|
+
processedAmount = BigNumber(processedAmount).toFormat();
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
// Adding periods, if the amount was shortened
|
|
1885
|
+
if (params.periods && addPeriods && !wereMillionsCollapsed) {
|
|
1886
|
+
processedAmount = processedAmount + this.periods;
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
// Adding an adaptive (printable/full) ticker
|
|
1890
|
+
if (params.ticker && ticker) {
|
|
1891
|
+
processedAmount = processedAmount + " " + ticker;
|
|
1892
|
+
}
|
|
1893
|
+
return processedAmount;
|
|
1894
|
+
} catch (e) {
|
|
1895
|
+
improveAndRethrow(e, "crypto", `Passed: ${amount}, ${ticker}, ${digits}`);
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
static _checkIfAmountInvalid(amount, allowNumbers = false) {
|
|
1899
|
+
return amount == null || amount === "" || !BigNumber.isBigNumber(amount) && typeof amount !== "string" && (!allowNumbers || typeof amount !== "number");
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
/**
|
|
1903
|
+
* Trims all digits after period that exceed the number of digits provided.
|
|
1904
|
+
* Use this everywhere when calculating some amount value to ensure the result is correct in terms
|
|
1905
|
+
* of max digits allowed by specific currency.
|
|
1906
|
+
*
|
|
1907
|
+
* @param amount {BigNumber|number|string|null|undefined} The number value to be trimmed.
|
|
1908
|
+
* HEX strings also allowed "0x..." and JS hex numbers
|
|
1909
|
+
* @param digits {number} allowed digits
|
|
1910
|
+
* @return {string|null} String with trimmed number or null for invalid amount
|
|
1911
|
+
*/
|
|
1912
|
+
static trim(amount, digits) {
|
|
1913
|
+
try {
|
|
1914
|
+
if (this._checkIfAmountInvalid(amount, true)) return null;
|
|
1915
|
+
return BigNumber(amount).toFixed(digits, BigNumber.ROUND_FLOOR);
|
|
1916
|
+
} catch (e) {
|
|
1917
|
+
improveAndRethrow(e, "trim", `Passed: ${amount}, ${digits}`);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
/**
|
|
1922
|
+
* @param amount {BigNumber|number|string|null|undefined} The number value to be trimmed.
|
|
1923
|
+
* HEX strings also allowed "0x..." and JS hex numbers
|
|
1924
|
+
* @return {string|null}
|
|
1925
|
+
*/
|
|
1926
|
+
static intStr(amount) {
|
|
1927
|
+
return this.trim(amount, 0);
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
/**
|
|
1931
|
+
* Shortens the line length by using a "1.52M" representation of big amounts.
|
|
1932
|
+
*
|
|
1933
|
+
* @param amountBigNumber {BigNumber} The number value to be trimmed
|
|
1934
|
+
* @param decimalCount {number} The number of digits after period to keep in millions representation
|
|
1935
|
+
* @param params {object} params object
|
|
1936
|
+
* @return {string} A shortened string, converted into "millions" format, if the amount exceeds 1 million
|
|
1937
|
+
*/
|
|
1938
|
+
static _collapseToMillionsAndFormat(amountBigNumber, decimalCount, params = {}) {
|
|
1939
|
+
try {
|
|
1940
|
+
// TODO: [feature, moderate] use local format here - take from JS locales (comma/dot etc.)
|
|
1941
|
+
const millionBigNumber = BigNumber("1000000");
|
|
1942
|
+
const millions = amountBigNumber.div(millionBigNumber).toFixed(decimalCount, BigNumber.ROUND_FLOOR);
|
|
1943
|
+
const limitedResult = this._limitTotalAmountLengthIfNeeded(millions, params);
|
|
1944
|
+
const formatted = BigNumber(limitedResult.processedAmount).toFormat();
|
|
1945
|
+
return formatted + "M";
|
|
1946
|
+
} catch (e) {
|
|
1947
|
+
improveAndRethrow(e, "_collapseAmountAndFormat", `Passed: ${amountBigNumber.toFixed()}, ${decimalCount}`);
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
/**
|
|
1952
|
+
* @param amountString {string} The amount to be restricted by length
|
|
1953
|
+
* @param params {object} Params object used for formatting
|
|
1954
|
+
* @return {{processedAmount:string, addPeriods: boolean}} A shortened string
|
|
1955
|
+
*/
|
|
1956
|
+
static _limitTotalAmountLengthIfNeeded(amountString, params) {
|
|
1957
|
+
try {
|
|
1958
|
+
let addPeriods = false;
|
|
1959
|
+
if (params.limitTotalLength || params.extraSmallLength) {
|
|
1960
|
+
const maxLength = params.extraSmallLength ? this.extraSmallMaxTotalLength : this.maxTotalLength;
|
|
1961
|
+
if (amountString.length > maxLength) {
|
|
1962
|
+
const delta = amountString.length - maxLength;
|
|
1963
|
+
const currentDecimalsCount = BigNumber(amountString).decimalPlaces();
|
|
1964
|
+
const newDecimalCount = currentDecimalsCount - delta;
|
|
1965
|
+
amountString = BigNumber(amountString).toFixed(newDecimalCount > 2 ? newDecimalCount : 2, BigNumber.ROUND_FLOOR);
|
|
1966
|
+
addPeriods = currentDecimalsCount > newDecimalCount;
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
return {
|
|
1970
|
+
addPeriods: addPeriods,
|
|
1971
|
+
processedAmount: amountString
|
|
1972
|
+
};
|
|
1973
|
+
} catch (e) {
|
|
1974
|
+
improveAndRethrow(e, "_limitTotalAmountLengthIfNeeded", `Passed: ${amountString}, ${params}`);
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
/**
|
|
1979
|
+
* Safely composes rate string (handles small/big rates)
|
|
1980
|
+
*
|
|
1981
|
+
* @param leftTicker {string}
|
|
1982
|
+
* @param rightTicker {string}
|
|
1983
|
+
* @param rate {number|string|BigNumber}
|
|
1984
|
+
* @param [rightCurrencyDigitsAfterDots=8] {number}
|
|
1985
|
+
* @return {string}
|
|
1986
|
+
*/
|
|
1987
|
+
static composeRateText(leftTicker, rightTicker, rate, rightCurrencyDigitsAfterDots = this.significantDecimalCount) {
|
|
1988
|
+
try {
|
|
1989
|
+
/* Here we try to calculate a clear rate for the user. The difficulty is that the rate value can be pretty
|
|
1990
|
+
* small as some coins have significantly higher price than the other. For such cases we calculate
|
|
1991
|
+
* not the "1 <coin_A> is X <coin B>" but "Y <coin_A> is X <coin B>" where Y is one of the powers of 100.
|
|
1992
|
+
*/
|
|
1993
|
+
let leftNumber = BigNumber("1");
|
|
1994
|
+
const multiplier = BigNumber("100");
|
|
1995
|
+
const maxAttemptsToGetRate = 10;
|
|
1996
|
+
let right = null;
|
|
1997
|
+
const rateBigNumber = BigNumber(rate);
|
|
1998
|
+
for (let i = 0; i < maxAttemptsToGetRate; ++i) {
|
|
1999
|
+
const rightNumberAttempt = rateBigNumber.times(leftNumber).toFixed(rightCurrencyDigitsAfterDots, BigNumber.ROUND_FLOOR);
|
|
2000
|
+
if (!BigNumber(rightNumberAttempt).eq(BigNumber("0"))) {
|
|
2001
|
+
right = BigNumber(rightNumberAttempt);
|
|
2002
|
+
break;
|
|
2003
|
+
} else {
|
|
2004
|
+
leftNumber = leftNumber.times(multiplier);
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
const leftAmountString = AmountUtils.intStr(leftNumber);
|
|
2008
|
+
const rightAmountString = right != null ? right.toFixed(rightCurrencyDigitsAfterDots, BigNumber.ROUND_FLOOR) : null;
|
|
2009
|
+
return `${leftAmountString} ${leftTicker} ~ ${rightAmountString != null ? rightAmountString : "?"} ${rightTicker}`;
|
|
2010
|
+
} catch (e) {
|
|
2011
|
+
// eslint-disable-next-line no-console
|
|
2012
|
+
console.log("composeRateText", e);
|
|
2013
|
+
}
|
|
2014
|
+
return "-";
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
/**
|
|
2018
|
+
* @param numberAsAString {string}
|
|
2019
|
+
* @return {string}
|
|
2020
|
+
*/
|
|
2021
|
+
static removeRedundantRightZerosFromNumberString(numberAsAString) {
|
|
2022
|
+
try {
|
|
2023
|
+
var _right2;
|
|
2024
|
+
const parts = ("" + numberAsAString).split(".");
|
|
2025
|
+
let right = parts[1];
|
|
2026
|
+
while ((_right = right) != null && _right.length && right[right.length - 1] === "0") {
|
|
2027
|
+
var _right;
|
|
2028
|
+
right = right.slice(0, right.length - 1);
|
|
2029
|
+
}
|
|
2030
|
+
return `${parts[0]}${(_right2 = right) != null && _right2.length ? `.${right}` : ""}`;
|
|
2031
|
+
} catch (e) {
|
|
2032
|
+
improveAndRethrow(e, "removeRedundantRightZerosFromNumberString", `Passed: ${numberAsAString}`);
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
AmountUtils.significantDecimalCount = 8;
|
|
2037
|
+
AmountUtils.collapsedDecimalCount = 2;
|
|
2038
|
+
AmountUtils.maxTotalLength = 12;
|
|
2039
|
+
AmountUtils.extraSmallMaxTotalLength = 9;
|
|
2040
|
+
// >=10 breaks transactions list (mobile) and it is hard to avoid this
|
|
2041
|
+
AmountUtils.periods = "..";
|
|
2042
|
+
AmountUtils.defaultFiatParams = {
|
|
2043
|
+
ticker: true,
|
|
2044
|
+
// If true, currency code will be shown
|
|
2045
|
+
enableCurrencySymbols: true,
|
|
2046
|
+
// Enables currency symbols where available. Requires "ticker: true"
|
|
2047
|
+
collapsible: true,
|
|
2048
|
+
// Enables minimization of amounts over 1 million (example: 1.52M)
|
|
2049
|
+
limitTotalLength: true,
|
|
2050
|
+
// Limits the total amount length to maxTotalLength
|
|
2051
|
+
extraSmallLength: false // Limits the total amount length to extraSmallMaxTotalLength
|
|
2052
|
+
};
|
|
2053
|
+
AmountUtils.defaultCryptoParams = {
|
|
2054
|
+
ticker: true,
|
|
2055
|
+
// If true, asset ticker will be shown
|
|
2056
|
+
collapsible: true,
|
|
2057
|
+
// Enables minimization of amounts over 1 million (example: 1.52M)
|
|
2058
|
+
trim: true,
|
|
2059
|
+
// Cuts the right part of the amount if necessary, and adds ".." in the end
|
|
2060
|
+
limitTotalLength: true,
|
|
2061
|
+
// Limits the total amount length to maxTotalLength
|
|
2062
|
+
extraSmallLength: false,
|
|
2063
|
+
// Limits the total amount length to extraSmallMaxTotalLength
|
|
2064
|
+
periods: true,
|
|
2065
|
+
// Whether we add periods ("..") as suffix for trimmed numbers
|
|
2066
|
+
numberPartsSeparator: true // Whether we add separators e.g. for 1000000 -> 1,000,000
|
|
2067
|
+
};
|
|
2068
|
+
|
|
2069
|
+
class Blockchain {
|
|
2070
|
+
/**
|
|
2071
|
+
* @param name {string} latin printable name of blockchain
|
|
2072
|
+
* @param supportedProtocols {Protocol[]}
|
|
2073
|
+
*/
|
|
2074
|
+
constructor(name, supportedProtocols = []) {
|
|
2075
|
+
this.name = name;
|
|
2076
|
+
this.supportedProtocols = supportedProtocols;
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
class Protocol {
|
|
2081
|
+
constructor(protocolName) {
|
|
2082
|
+
this.protocol = protocolName;
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
/**
|
|
2087
|
+
* The model for cryptocurrency coins.
|
|
2088
|
+
*
|
|
2089
|
+
* WARNING: this class should not be instantiated directly. Use only predefined singleton Coin (or descendants) instances.
|
|
2090
|
+
*/
|
|
2091
|
+
class Coin {
|
|
2092
|
+
/**
|
|
2093
|
+
* Creates new coin
|
|
2094
|
+
*
|
|
2095
|
+
* @param latinName {string} the coin name in latin symbols like "Bitcoin"
|
|
2096
|
+
* @param ticker {string} the coin symbol/ticker/code like 'BTC'. Always upper case. A unique coin identifier
|
|
2097
|
+
* @param tickerPrintable {string} ticker but in printable format. Useful for tokens based on external blockchains
|
|
2098
|
+
* like ERC20 or TRC20. It is not friendly to display USDTERC20 or BUSDTRC20 - more neat options are just
|
|
2099
|
+
* USDT and BUSD. Note that you should always care about user's understanding of what coin he/she is working
|
|
2100
|
+
* with as printable ticker for USDTERC20 and USDTTRC20 are the same.
|
|
2101
|
+
* @param digitsCountAfterComma {number} count of digits after the comma. E.g. 8 for bitcoin
|
|
2102
|
+
* @param maxValue {number|null} max possible value for cryptocurrency. Null means that the currency has no max possible value
|
|
2103
|
+
* @param atomName {string} name of the coin's atomic value. Like 'satoshi' for bitcoin
|
|
2104
|
+
* @param mainnet {Network} main network for this coin
|
|
2105
|
+
* @param testnet {Network} test network for this coin
|
|
2106
|
+
* @param minConfirmations {number} min confirmations count to treat the coin's transaction confirmed
|
|
2107
|
+
* @param payableEntityStringForFeeRate {string|null} the payable fee entity like byte for bitcoin or gas for ether if present
|
|
2108
|
+
* @param feeOptionsTimeStringsSortedDesc {string[]} array of 4 strings for fee options when sending coins. Should be sorted from the highest time to the smallest
|
|
2109
|
+
* @param feeRatesExpirationTimeMs {number} number of milliseconds to treat the fee rates as expired
|
|
2110
|
+
* @param blockchain {Blockchain} blockchain object
|
|
2111
|
+
* @param [protocol] {Protocol|null} token/coin protocol if relevant
|
|
2112
|
+
* @param [tokenAddress] {string|null} address of contract of this token (if the coin is token)
|
|
2113
|
+
* @param [doesUseLowerCaseAddresses] {boolean} flag to clarify whether we can use lower case addresses to ensure more robust comparisons
|
|
2114
|
+
* @param [doesUseOutputs=false] {boolean} true if this coin uses inputs/outputs concept and false if it uses just balances
|
|
2115
|
+
*/
|
|
2116
|
+
constructor(latinName, ticker, tickerPrintable, digitsCountAfterComma, maxValue, atomName, mainnet, testnet, minConfirmations, payableEntityStringForFeeRate, feeOptionsTimeStringsSortedDesc, feeRatesExpirationTimeMs, blockchain, protocol = null, tokenAddress = null, doesUseLowerCaseAddresses = true, doesUseOutputs = false) {
|
|
2117
|
+
this.latinName = latinName;
|
|
2118
|
+
this.ticker = ticker;
|
|
2119
|
+
this.tickerPrintable = tickerPrintable;
|
|
2120
|
+
this.digits = digitsCountAfterComma;
|
|
2121
|
+
this.maxValue = maxValue;
|
|
2122
|
+
this.atomName = atomName;
|
|
2123
|
+
this.mainnet = mainnet;
|
|
2124
|
+
this.testnet = testnet;
|
|
2125
|
+
this.minConfirmations = minConfirmations;
|
|
2126
|
+
this.payableEntityStringForFeeRate = payableEntityStringForFeeRate;
|
|
2127
|
+
this.feeOptionsTimeStringsSortedDesc = feeOptionsTimeStringsSortedDesc;
|
|
2128
|
+
this.feeRatesExpirationTimeMs = feeRatesExpirationTimeMs;
|
|
2129
|
+
this.protocol = protocol;
|
|
2130
|
+
this.blockchain = blockchain;
|
|
2131
|
+
// TODO: [bug, critical] use testnet property for testnet contract address as it blocks the app work in testnets
|
|
2132
|
+
this.tokenAddress = tokenAddress;
|
|
2133
|
+
this.feeCoin = this;
|
|
2134
|
+
this._significantDigits = 8;
|
|
2135
|
+
this.doesUseLowerCaseAddresses = doesUseLowerCaseAddresses;
|
|
2136
|
+
this.doesUseOutputs = doesUseOutputs;
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
/**
|
|
2140
|
+
* Sets fee coin
|
|
2141
|
+
*
|
|
2142
|
+
* @param feeCoin {Coin} some tokens use another coin to charge transaction fee as they work on top of some external
|
|
2143
|
+
* blockchain. So pass here the coin the token uses for fee charging. Like for ERC20 token the fee coin is ETH.
|
|
2144
|
+
* By default, the creating coin will be set as a value for this field.
|
|
2145
|
+
*/
|
|
2146
|
+
setFeeCoin(feeCoin) {
|
|
2147
|
+
this.feeCoin = feeCoin;
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
/**
|
|
2151
|
+
* Checks whether this coin uses another coin (blockchain) to charge fee for transactions (means works on base of
|
|
2152
|
+
* some external blockchain).
|
|
2153
|
+
*
|
|
2154
|
+
* @return {boolean} true if this coin uses external blockchain to perform transactions and charge fee
|
|
2155
|
+
*/
|
|
2156
|
+
doesUseDifferentCoinFee() {
|
|
2157
|
+
return this.feeCoin !== this;
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
/**
|
|
2161
|
+
* Converts the given atoms string/number to string representing the same amount in coin itself - floating point number
|
|
2162
|
+
*
|
|
2163
|
+
* @param atoms {string} atoms positive integer amount
|
|
2164
|
+
* @return {string} coin amount floating point number as a string
|
|
2165
|
+
*/
|
|
2166
|
+
atomsToCoinAmount(atoms) {
|
|
2167
|
+
throw new Error("Not implemented in base Coin");
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
/**
|
|
2171
|
+
* Converts the given coins amount string/number to string representing the same amount in coin atoms - integer number
|
|
2172
|
+
*
|
|
2173
|
+
* @param coinsAmount {string} coins positive floating point amount
|
|
2174
|
+
* @return {string} coin atoms amount integer number as a string
|
|
2175
|
+
*/
|
|
2176
|
+
coinAmountToAtoms(coinsAmount) {
|
|
2177
|
+
throw new Error("Not implemented in base Coin");
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
/**
|
|
2181
|
+
* Composes URL to view the tx with given id in the external blockchain explorer
|
|
2182
|
+
*
|
|
2183
|
+
* @param txId {string} id of transaction
|
|
2184
|
+
* @return {string} URL string
|
|
2185
|
+
*/
|
|
2186
|
+
composeUrlToTransactionExplorer(txId) {
|
|
2187
|
+
throw new Error("Not implemented in base Coin");
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
/**
|
|
2191
|
+
* Most of the cryptocurrencies has specific fee rate or fee price metric. This value usually has specific measure
|
|
2192
|
+
* like satoshi/byte or gWei/gas. This function adds the described denomination string to the given amount
|
|
2193
|
+
* as a suffix and returns the result string ready to be show to a user.
|
|
2194
|
+
*
|
|
2195
|
+
* @param coinAtomsString {string} coin atoms positive integer amount
|
|
2196
|
+
* @return {string} string of coin amount and fee rate units
|
|
2197
|
+
*/
|
|
2198
|
+
coinAtomsFeeRateToCommonlyUsedAmountFormatWithDenominationString(coinAtomsString) {
|
|
2199
|
+
throw new Error("Not implemented in base Coin");
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
/**
|
|
2203
|
+
* Check whether this coin support transaction prioritisation during the sending process.
|
|
2204
|
+
*
|
|
2205
|
+
* @return {boolean} true if support transaction prioritisation and false otherwise
|
|
2206
|
+
*/
|
|
2207
|
+
doesSupportTransactionPrioritisation() {
|
|
2208
|
+
return Array.isArray(this.feeOptionsTimeStringsSortedDesc);
|
|
2209
|
+
}
|
|
2210
|
+
tickerAndProtocol() {
|
|
2211
|
+
try {
|
|
2212
|
+
var _ref;
|
|
2213
|
+
return `${this.tickerPrintable}${this.protocol ? (_ref = " " + this.protocol.protocol) != null ? _ref : "" : ""}`;
|
|
2214
|
+
} catch (e) {
|
|
2215
|
+
improveAndRethrow(e, "tickerAndProtocol");
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
/**
|
|
2221
|
+
* TODO: [tests, critical] Ued by payments logic
|
|
2222
|
+
*
|
|
2223
|
+
* Simple cache based on Map.
|
|
2224
|
+
* Provides ability to store event-dependent data.
|
|
2225
|
+
*/
|
|
2226
|
+
class Cache {
|
|
2227
|
+
/**
|
|
2228
|
+
* @param eventBus {EventBus} EventBus.js lib instance
|
|
2229
|
+
* @param [noSessionEvents=[]] {string[]} array of events that will be treated as "no session"
|
|
2230
|
+
*/
|
|
2231
|
+
constructor(eventBus, noSessionEvents = []) {
|
|
2232
|
+
this._cache = new Map();
|
|
2233
|
+
this._eventDependentDataKeys = [];
|
|
2234
|
+
this._noSessionEvents = noSessionEvents;
|
|
2235
|
+
this._eventBus = eventBus;
|
|
2236
|
+
}
|
|
2237
|
+
_setupIntervalClearingExpired() {
|
|
2238
|
+
let _cleaner = function cleaner() {
|
|
2239
|
+
try {
|
|
2240
|
+
for (const key of this._cache.keys()) {
|
|
2241
|
+
const item = this._cache.get(key);
|
|
2242
|
+
if (item && item.ttlMs && item.addedMsTimestamp + item.ttlMs < Date.now()) {
|
|
2243
|
+
this._cache.delete(key);
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
} catch (e) {
|
|
2247
|
+
improveAndRethrow(e, "_intervalClearingExpiredCache");
|
|
2248
|
+
}
|
|
2249
|
+
};
|
|
2250
|
+
_cleaner = _cleaner.bind(this);
|
|
2251
|
+
setInterval(_cleaner, 1000);
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
/**
|
|
2255
|
+
* Puts data to cache
|
|
2256
|
+
*
|
|
2257
|
+
* @param key {string} string key for this data
|
|
2258
|
+
* @param data {any} any data
|
|
2259
|
+
* @param ttlMs {number|null} optional milliseconds number for cache lifetime
|
|
2260
|
+
* @throws {Error} when the data is null/undefined because these values for data are reserved for internal logic
|
|
2261
|
+
*/
|
|
2262
|
+
put(key, data, ttlMs = null) {
|
|
2263
|
+
try {
|
|
2264
|
+
if (typeof key !== "string" || data == null) {
|
|
2265
|
+
throw new Error(`Trying to cache corrupted data: ${key}, ${data}`);
|
|
2266
|
+
}
|
|
2267
|
+
this._cache.set(key, {
|
|
2268
|
+
data: data,
|
|
2269
|
+
addedMsTimestamp: Date.now(),
|
|
2270
|
+
ttlMs: ttlMs
|
|
2271
|
+
});
|
|
2272
|
+
} catch (e) {
|
|
2273
|
+
improveAndRethrow(e, "cache.put");
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
putSessionDependentData(key, data, ttlMs = null) {
|
|
2277
|
+
this._putEventDependentData(key, data, this._noSessionEvents, ttlMs);
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
/**
|
|
2281
|
+
* Puts data to cache and adds its key to list of keys that should be related by each of given events.
|
|
2282
|
+
*
|
|
2283
|
+
* @param key {string} key for cache
|
|
2284
|
+
* @param data {any} any caching data
|
|
2285
|
+
* @param events {string[]} list of events forcing putting data to be removed when triggered
|
|
2286
|
+
* @param ttlMs {|null} optional time to live for this cache item
|
|
2287
|
+
* @throws {Error} when the data is null/undefined because these values for data are reserved for internal logic
|
|
2288
|
+
*/
|
|
2289
|
+
putEventDependentData(key, data, events, ttlMs = null) {
|
|
2290
|
+
this._putEventDependentData(key, data, events, ttlMs);
|
|
2291
|
+
}
|
|
2292
|
+
_putEventDependentData(key, data, events, ttlMs = null) {
|
|
2293
|
+
try {
|
|
2294
|
+
if (typeof key !== "string" || data == null) {
|
|
2295
|
+
throw new Error(`Trying to cache corrupted data: ${key}, ${data}`);
|
|
2296
|
+
}
|
|
2297
|
+
this._cache.set(key, {
|
|
2298
|
+
data: data,
|
|
2299
|
+
addedMsTimestamp: Date.now(),
|
|
2300
|
+
ttlMs: ttlMs
|
|
2301
|
+
});
|
|
2302
|
+
for (let event of events) {
|
|
2303
|
+
const eventAndKeys = this._eventDependentDataKeys.find(item => item[0] === event);
|
|
2304
|
+
if (eventAndKeys) {
|
|
2305
|
+
eventAndKeys.push(key);
|
|
2306
|
+
} else {
|
|
2307
|
+
this._eventDependentDataKeys.push([event, key]);
|
|
2308
|
+
this._eventBus.addEventListener(event, () => {
|
|
2309
|
+
try {
|
|
2310
|
+
const keys = this._eventDependentDataKeys.find(item => item[0] === event);
|
|
2311
|
+
(keys != null ? keys : [event]).slice(1).forEach(key => this._cache.delete(key));
|
|
2312
|
+
} catch (e) {
|
|
2313
|
+
Logger.logError(e, "cache.removing-for-event", `Event: ${event}`);
|
|
2314
|
+
}
|
|
2315
|
+
});
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
} catch (e) {
|
|
2319
|
+
improveAndRethrow(e, "cache.putEventDependentData");
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
// TODO: [feature, low] add clearing of expired data by schedule
|
|
2324
|
+
get(key) {
|
|
2325
|
+
try {
|
|
2326
|
+
const item = this._cache.get(key);
|
|
2327
|
+
if (item) {
|
|
2328
|
+
if (item.addedMsTimestamp && item.ttlMs !== null && item.addedMsTimestamp + item.ttlMs < Date.now()) {
|
|
2329
|
+
this._cache.delete(key);
|
|
2330
|
+
return null;
|
|
2331
|
+
} else {
|
|
2332
|
+
return item.data;
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
return null;
|
|
2336
|
+
} catch (e) {
|
|
2337
|
+
improveAndRethrow(e, "cache.get");
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
getLastUpdateTimestamp(key) {
|
|
2341
|
+
var _this$_cache$get$adde, _this$_cache$get;
|
|
2342
|
+
return (_this$_cache$get$adde = (_this$_cache$get = this._cache.get(key)) == null ? void 0 : _this$_cache$get.addedMsTimestamp) != null ? _this$_cache$get$adde : null;
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
/**
|
|
2346
|
+
* Updates the timestamp of the last update for specified key to the provided value.
|
|
2347
|
+
* Can be useful when TTL is controlled outside this class.
|
|
2348
|
+
*
|
|
2349
|
+
* @param key {string}
|
|
2350
|
+
* @param timestamp {number}
|
|
2351
|
+
* @return {boolean}
|
|
2352
|
+
*/
|
|
2353
|
+
setLastUpdateTimestamp(key, timestamp) {
|
|
2354
|
+
try {
|
|
2355
|
+
const item = this._cache.get(key);
|
|
2356
|
+
if (item != null && typeof timestamp === "number") {
|
|
2357
|
+
this._cache.set(key, _extends({}, item, {
|
|
2358
|
+
addedTimestampMs: timestamp
|
|
2359
|
+
}));
|
|
2360
|
+
return true;
|
|
2361
|
+
}
|
|
2362
|
+
return false;
|
|
2363
|
+
} catch (e) {
|
|
2364
|
+
improveAndRethrow("cache.setLastUpdateTimestamp");
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
invalidate(key) {
|
|
2368
|
+
try {
|
|
2369
|
+
this._cache.delete(key);
|
|
2370
|
+
} catch (e) {
|
|
2371
|
+
improveAndRethrow(e, "cache.invalidate");
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
invalidateContaining(keyPart) {
|
|
2375
|
+
if (typeof keyPart !== "string" || keyPart === "") {
|
|
2376
|
+
throw new Error("Trying to invalidate containing wrong key or empty key: " + keyPart);
|
|
2377
|
+
}
|
|
2378
|
+
try {
|
|
2379
|
+
const matchedKeys = Array.from(this._cache.keys()).filter(key => typeof key === "string" && new RegExp(keyPart).test(key));
|
|
2380
|
+
for (let i = 0; i < matchedKeys.length; ++i) {
|
|
2381
|
+
this._cache.delete(matchedKeys[i]);
|
|
2382
|
+
}
|
|
2383
|
+
} catch (e) {
|
|
2384
|
+
improveAndRethrow(e, "invalidateContaining");
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
clear() {
|
|
2388
|
+
this._cache.clear();
|
|
2389
|
+
this._sessionDependentDataKeys = [];
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
/**
|
|
2393
|
+
* Saves given data string to persistent cache.
|
|
2394
|
+
* NOTE: we have no TTL here, implement if needed.
|
|
2395
|
+
*
|
|
2396
|
+
* WARNING: use only when really needed and don't store big data as we use localStorage
|
|
2397
|
+
* under the hood and its capacity is restricted.
|
|
2398
|
+
*
|
|
2399
|
+
* @param uniqueKey {string} the key should be unique
|
|
2400
|
+
* @param data {string} only string data allowed
|
|
2401
|
+
*/
|
|
2402
|
+
putClientPersistentData(uniqueKey, data) {
|
|
2403
|
+
try {
|
|
2404
|
+
if (typeof window !== "undefined") {
|
|
2405
|
+
localStorage.setItem(uniqueKey, data);
|
|
2406
|
+
}
|
|
2407
|
+
} catch (e) {
|
|
2408
|
+
improveAndRethrow(e, "cache.putClientPersistentData");
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
/**
|
|
2413
|
+
* @param uniqueKey {string}
|
|
2414
|
+
* @return {string|null}
|
|
2415
|
+
*/
|
|
2416
|
+
getClientPersistentData(uniqueKey) {
|
|
2417
|
+
try {
|
|
2418
|
+
if (typeof window !== "undefined") {
|
|
2419
|
+
return localStorage.getItem(uniqueKey);
|
|
2420
|
+
}
|
|
2421
|
+
return null;
|
|
2422
|
+
} catch (e) {
|
|
2423
|
+
improveAndRethrow(e, "cache.getClientPersistentData");
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
/**
|
|
2428
|
+
* Only makes effect if the TTL is not null.
|
|
2429
|
+
*
|
|
2430
|
+
* @param key {string}
|
|
2431
|
+
* @param ttlMs {number|null}
|
|
2432
|
+
*/
|
|
2433
|
+
markCacheItemAsExpiredButDontRemove(key, ttlMs = null) {
|
|
2434
|
+
try {
|
|
2435
|
+
const item = this._cache.get(key);
|
|
2436
|
+
const ttlFinalMs = ttlMs != null ? ttlMs : item == null ? void 0 : item.ttlMs;
|
|
2437
|
+
if (item != null && ttlFinalMs) {
|
|
2438
|
+
this._cache.set(key, {
|
|
2439
|
+
data: item.data,
|
|
2440
|
+
addedMsTimestamp: Date.now() - ttlFinalMs - 1,
|
|
2441
|
+
ttlMs: ttlFinalMs
|
|
2442
|
+
});
|
|
2443
|
+
}
|
|
2444
|
+
} catch (e) {
|
|
2445
|
+
improveAndRethrow(e, "cache.markCacheItemAsExpiredButDontRemove");
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
function postponeExecution(execution, timeoutMS = 1000) {
|
|
2451
|
+
return new Promise((resolve, reject) => {
|
|
2452
|
+
setTimeout(async () => {
|
|
2453
|
+
try {
|
|
2454
|
+
resolve(await execution());
|
|
2455
|
+
} catch (e) {
|
|
2456
|
+
reject(e);
|
|
2457
|
+
}
|
|
2458
|
+
}, timeoutMS);
|
|
2459
|
+
});
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
class AxiosAdapter {
|
|
2463
|
+
static async call(method, ...args) {
|
|
2464
|
+
return await axios[method](...args);
|
|
2465
|
+
}
|
|
2466
|
+
static async get(...args) {
|
|
2467
|
+
return await axios.get(...args);
|
|
2468
|
+
}
|
|
2469
|
+
static async post(...args) {
|
|
2470
|
+
return await axios.post(...args);
|
|
2471
|
+
}
|
|
2472
|
+
static async put(...args) {
|
|
2473
|
+
return await axios.put(...args);
|
|
2474
|
+
}
|
|
2475
|
+
static async delete(...args) {
|
|
2476
|
+
return await axios.delete(...args);
|
|
2477
|
+
}
|
|
2478
|
+
static async patch(...args) {
|
|
2479
|
+
return await axios.patch(...args);
|
|
2480
|
+
}
|
|
2481
|
+
static async options(...args) {
|
|
2482
|
+
return await axios.options(...args);
|
|
2483
|
+
}
|
|
2484
|
+
static async head(...args) {
|
|
2485
|
+
return await axios.head(...args);
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
class EmailsApi {
|
|
2490
|
+
static async sendEmail(subject, body) {
|
|
2491
|
+
try {
|
|
2492
|
+
const url = `${window.location.protocol + "//" + window.location.host}/api/v1/${this.serverEndpointEntity}`;
|
|
2493
|
+
await axios.post(url, {
|
|
2494
|
+
subject,
|
|
2495
|
+
body
|
|
2496
|
+
});
|
|
2497
|
+
} catch (e) {
|
|
2498
|
+
improveAndRethrow(e, "sendEmail", subject + body);
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
EmailsApi.serverEndpointEntity = "emails";
|
|
2503
|
+
|
|
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 {
|
|
2531
|
+
/**
|
|
2532
|
+
* @param bio {string} unique identifier for the exact service
|
|
2533
|
+
* @param cache {Cache} cache
|
|
2534
|
+
* @param cacheTtl {number|null} time to live for cache ms. 0 or null means the cache cannot expire
|
|
2535
|
+
* @param [maxCallAttemptsToWaitForAlreadyRunningRequest=100] {number} number of request allowed to do waiting for
|
|
2536
|
+
* result before we fail the original request. Use custom value only if you need to make the attempts count
|
|
2537
|
+
* and polling interval changes.
|
|
2538
|
+
* @param [timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished=1000] {number}
|
|
2539
|
+
* timeout ms for polling for a result. if you change maxCallAttemptsToWaitForAlreadyRunningRequest
|
|
2540
|
+
* then this parameter maybe also require the custom value.
|
|
2541
|
+
* @param [removeExpiredCacheAutomatically=true] {boolean}
|
|
2542
|
+
*/
|
|
2543
|
+
constructor(bio, cache, cacheTtl, removeExpiredCacheAutomatically = true, maxCallAttemptsToWaitForAlreadyRunningRequest = 100, timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished = 1000) {
|
|
2544
|
+
if (cacheTtl != null && cacheTtl < timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished * 2) {
|
|
2545
|
+
/*
|
|
2546
|
+
* During the lifetime of this service e.g. if the data is being retrieved slowly we can get
|
|
2547
|
+
* RACE CONDITION when we constantly retrieve data and during retrieval it is expired, so we are trying
|
|
2548
|
+
* to retrieve it again and again.
|
|
2549
|
+
* We have a protection mechanism that we will wait no more than
|
|
2550
|
+
* maxCallAttemptsToWaitForAlreadyRunningRequest * timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished
|
|
2551
|
+
* but this additional check is aimed to reduce potential loading time for some requests.
|
|
2552
|
+
*/
|
|
2553
|
+
throw new Error(`DEV: Wrong parameters passed to construct ${bio} - TTL ${cacheTtl} should be 2 times greater than ${timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished}`);
|
|
2554
|
+
}
|
|
2555
|
+
this._bio = bio;
|
|
2556
|
+
this._cache = cache;
|
|
2557
|
+
this._cacheTtlMs = cacheTtl != null ? cacheTtl : null;
|
|
2558
|
+
this._maxExecutionTimeMs = maxCallAttemptsToWaitForAlreadyRunningRequest * timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished;
|
|
2559
|
+
this._removeExpiredCacheAutomatically = removeExpiredCacheAutomatically;
|
|
2560
|
+
this._requestsManager = new ManagerOfRequestsToTheSameResource(bio, maxCallAttemptsToWaitForAlreadyRunningRequest, timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished);
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
/**
|
|
2564
|
+
* When using this service this is the major method you should call to get data by cache id.
|
|
2565
|
+
* This method checks is there cached data and ether
|
|
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.
|
|
2574
|
+
*
|
|
2575
|
+
* @param cacheId {string}
|
|
2576
|
+
* @return {Promise<({
|
|
2577
|
+
* canStartDataRetrieval: true,
|
|
2578
|
+
* cachedData: any,
|
|
2579
|
+
* lockId: string
|
|
2580
|
+
* }|{
|
|
2581
|
+
* canStartDataRetrieval: false,
|
|
2582
|
+
* cachedData: any
|
|
2583
|
+
* })>}
|
|
2584
|
+
*/
|
|
2585
|
+
async getCachedOrWaitForCachedOrAcquireLock(cacheId) {
|
|
2586
|
+
try {
|
|
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
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
/**
|
|
2633
|
+
* Returns just the current cache value for the given id.
|
|
2634
|
+
* Doesn't wait for the active calculation, doesn't acquire lock, just retrieves the current cache as it is.
|
|
2635
|
+
*
|
|
2636
|
+
* @param cacheId {string}
|
|
2637
|
+
* @return {any}
|
|
2638
|
+
*/
|
|
2639
|
+
getCached(cacheId) {
|
|
2640
|
+
try {
|
|
2641
|
+
return this._cache.get(cacheId);
|
|
2642
|
+
} catch (e) {
|
|
2643
|
+
improveAndRethrow(e, "getCached");
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
_getTtl() {
|
|
2647
|
+
return this._removeExpiredCacheAutomatically ? this._cacheTtlMs : null;
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
/**
|
|
2651
|
+
* Directly acquires the lock despite on cached data availability.
|
|
2652
|
+
* So if this method returns result === true you can start the data retrieval.
|
|
2653
|
+
*
|
|
2654
|
+
* @param cacheId {string}
|
|
2655
|
+
* @return {Promise<{ result: true, lockId: string }|{ result: false }>}
|
|
2656
|
+
*/
|
|
2657
|
+
async acquireLock(cacheId) {
|
|
2658
|
+
try {
|
|
2659
|
+
return await this._requestsManager.acquireLock(cacheId);
|
|
2660
|
+
} catch (e) {
|
|
2661
|
+
improveAndRethrow(e, "acquireLock");
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
/**
|
|
2666
|
+
* This method should be called only if you acquired a lock successfully.
|
|
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.
|
|
2673
|
+
*
|
|
2674
|
+
* @param cacheId {string}
|
|
2675
|
+
* @param lockId {string}
|
|
2676
|
+
* @param data {any}
|
|
2677
|
+
* @param [sessionDependentData=true] {boolean}
|
|
2678
|
+
* @param [wasDataMergedSynchronouslyWithMostRecentCacheState=false]
|
|
2679
|
+
*/
|
|
2680
|
+
saveCachedData(cacheId, lockId, data, sessionDependentData = true, wasDataMergedSynchronouslyWithMostRecentCacheState = false) {
|
|
2681
|
+
try {
|
|
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
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
/**
|
|
2707
|
+
* Should be called then and only then if you successfully acquired a lock with the lock id.
|
|
2708
|
+
*
|
|
2709
|
+
* @param cacheId {string}
|
|
2710
|
+
* @param lockId {string}
|
|
2711
|
+
*/
|
|
2712
|
+
releaseLock(cacheId, lockId) {
|
|
2713
|
+
try {
|
|
2714
|
+
if (this._requestsManager.isTheLockActiveOne(cacheId, lockId)) {
|
|
2715
|
+
this._requestsManager.finishActiveCalculation(cacheId);
|
|
2716
|
+
}
|
|
2717
|
+
} catch (e) {
|
|
2718
|
+
improveAndRethrow(e, `${this._bio}.releaseLock`);
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
/**
|
|
2723
|
+
* Actualized currently present cached data by key. Applies the provided function to the cached data.
|
|
2724
|
+
*
|
|
2725
|
+
* @param cacheId {string} id of cache entry
|
|
2726
|
+
* @param synchronousCurrentCacheProcessor (function|null} synchronous function accepting cache entry. Should return
|
|
2727
|
+
* an object in following format:
|
|
2728
|
+
* {
|
|
2729
|
+
* isModified: boolean,
|
|
2730
|
+
* data: any
|
|
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
|
|
2734
|
+
*/
|
|
2735
|
+
actualizeCachedData(cacheId, synchronousCurrentCacheProcessor, sessionDependent = true) {
|
|
2736
|
+
try {
|
|
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);
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
|
|
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
|
+
/**
|
|
2789
|
+
* @param bio {string} resource-related identifier for logging
|
|
2790
|
+
* @param [maxPollsCount=100] {number} max number of attempts to wait when waiting for a lock acquisition
|
|
2791
|
+
* @param [timeoutDuration=1000] {number} timeout between the polls for a lock acquisition
|
|
2792
|
+
*/
|
|
2793
|
+
constructor(bio, maxPollsCount = 100, timeoutDuration = 1000) {
|
|
2794
|
+
this.bio = bio;
|
|
2795
|
+
this.maxPollsCount = maxPollsCount;
|
|
2796
|
+
this.timeoutDuration = timeoutDuration;
|
|
2797
|
+
this._activeCalculationsIds = new Map();
|
|
2798
|
+
this._nextCalculationIds = new Map();
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2801
|
+
/**
|
|
2802
|
+
* If there is no active calculation just creates uuid and returns it.
|
|
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
|
|
2808
|
+
*/
|
|
2809
|
+
async startCalculationOrWaitForActiveToFinish(requestHash) {
|
|
2810
|
+
try {
|
|
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;
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
/**
|
|
2825
|
+
* Acquires lock to the resource by the provided hash.
|
|
2826
|
+
*
|
|
2827
|
+
* @param requestHash {string}
|
|
2828
|
+
* @return {Promise<{ result: true, lockId: string }|{ result: false }>} result is true if the lock is successfully
|
|
2829
|
+
* acquired, false if the max allowed time to wait for acquisition expired or any unexpected error occurs
|
|
2830
|
+
* during the waiting.
|
|
2831
|
+
*/
|
|
2832
|
+
async acquireLock(requestHash) {
|
|
2833
|
+
try {
|
|
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
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2857
|
+
/**
|
|
2858
|
+
* Clears active calculation id.
|
|
2859
|
+
* WARNING: if you forget to call this method the start* one will perform maxPollsCount attempts before finishing
|
|
2860
|
+
* @param requestHash {string} hash of request. Helps to distinct the request for the same resource but
|
|
2861
|
+
* having different request parameters and hold a dedicated calculation id per this hash
|
|
2862
|
+
*/
|
|
2863
|
+
finishActiveCalculation(requestHash = "default") {
|
|
2864
|
+
try {
|
|
2865
|
+
var _this$_nextCalculatio2;
|
|
2866
|
+
this._activeCalculationsIds.delete(requestHash);
|
|
2867
|
+
const next = (_this$_nextCalculatio2 = this._nextCalculationIds.get(requestHash)) != null ? _this$_nextCalculatio2 : [];
|
|
2868
|
+
if (next.length) {
|
|
2869
|
+
this._activeCalculationsIds.set(requestHash, next[0]);
|
|
2870
|
+
this._nextCalculationIds.set(requestHash, next.slice(1));
|
|
2871
|
+
}
|
|
2872
|
+
} catch (e) {
|
|
2873
|
+
improveAndRethrow(e, "finishActiveCalculation");
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
finishAllActiveCalculations(keyPart = "") {
|
|
2877
|
+
try {
|
|
2878
|
+
Array.from(this._activeCalculationsIds.keys()).forEach(hash => {
|
|
2879
|
+
if (typeof hash === "string" && new RegExp(keyPart).test(hash)) {
|
|
2880
|
+
this.finishActiveCalculation(hash);
|
|
2881
|
+
}
|
|
2882
|
+
});
|
|
2883
|
+
} catch (e) {
|
|
2884
|
+
improveAndRethrow(e, "finishAllActiveCalculations");
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
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
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
|
|
2901
|
+
/**
|
|
2902
|
+
* @param requestHash {string}
|
|
2903
|
+
* @param activeCalculationId {string|null}
|
|
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
|
|
2912
|
+
*/
|
|
2913
|
+
async _waitForCalculationIdToFinish(requestHash, activeCalculationId, attemptIndex = 0, waitForCalculationId = null) {
|
|
2914
|
+
try {
|
|
2915
|
+
if (attemptIndex + 1 > this.maxPollsCount) {
|
|
2916
|
+
// Max number of polls for active calculation id change is achieved. So we return false.
|
|
2917
|
+
return false;
|
|
2918
|
+
}
|
|
2919
|
+
const currentId = this._activeCalculationsIds.get(requestHash);
|
|
2920
|
+
if (waitForCalculationId == null ? currentId !== activeCalculationId : currentId === waitForCalculationId) {
|
|
2921
|
+
/* We return true depending on the usage of this function:
|
|
2922
|
+
* 1. if there is calculation id that we should wait for to become an active then we return true only
|
|
2923
|
+
* if this id becomes the active one.
|
|
2924
|
+
*
|
|
2925
|
+
* Theoretically we can fail to wait for the desired calculation id. This can be caused by wrong use of
|
|
2926
|
+
* this service or by any other mistakes/errors. But this waiting function will return false anyway if
|
|
2927
|
+
* the number of polls done exceeds the max allowed.
|
|
2928
|
+
*
|
|
2929
|
+
* 2. if we just wait for the currently active calculation id to be finished then we return true
|
|
2930
|
+
* when we notice that the current active id differs from the original passed into this function.
|
|
2931
|
+
*/
|
|
2932
|
+
return true;
|
|
2933
|
+
} else {
|
|
2934
|
+
/* The original calculation id is still the active one, so we are scheduling a new attempt to check
|
|
2935
|
+
* whether the active calculation id changed or not in timeoutDuration milliseconds.
|
|
2936
|
+
*/
|
|
2937
|
+
const it = this;
|
|
2938
|
+
return new Promise((resolve, reject) => {
|
|
2939
|
+
setTimeout(function () {
|
|
2940
|
+
try {
|
|
2941
|
+
resolve(it._waitForCalculationIdToFinish(requestHash, activeCalculationId, attemptIndex + 1));
|
|
2942
|
+
} catch (e) {
|
|
2943
|
+
reject(e);
|
|
2944
|
+
}
|
|
2945
|
+
}, this.timeoutDuration);
|
|
2946
|
+
});
|
|
2947
|
+
}
|
|
2948
|
+
} catch (e) {
|
|
2949
|
+
Logger.logError(e, "_waitForCalculationIdToFinish", "Failed to wait for active calculation id change.");
|
|
2950
|
+
return false;
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2955
|
+
// TODO: [refactoring, low] Consider removing this logic task_id=c360f2af75764bde8badd9ff1cc00d48
|
|
2956
|
+
class ConcurrentCalculationsMetadataHolder {
|
|
2957
|
+
constructor() {
|
|
2958
|
+
this._calculations = {};
|
|
2959
|
+
}
|
|
2960
|
+
startCalculation(domain, calculationsHistoryMaxLength = 100) {
|
|
2961
|
+
if (!this._calculations[domain]) {
|
|
2962
|
+
this._calculations[domain] = [];
|
|
2963
|
+
}
|
|
2964
|
+
if (this._calculations[domain].length > calculationsHistoryMaxLength) {
|
|
2965
|
+
this._calculations[domain] = this._calculations[domain].slice(Math.round(calculationsHistoryMaxLength * 0.2));
|
|
2966
|
+
}
|
|
2967
|
+
const newCalculation = {
|
|
2968
|
+
startTimestamp: Date.now(),
|
|
2969
|
+
endTimestamp: null,
|
|
2970
|
+
uuid: v4()
|
|
2971
|
+
};
|
|
2972
|
+
this._calculations[domain].push(newCalculation);
|
|
2973
|
+
return newCalculation.uuid;
|
|
2974
|
+
}
|
|
2975
|
+
endCalculation(domain, uuid, isFailed = false) {
|
|
2976
|
+
try {
|
|
2977
|
+
var _calculation$endTimes, _calculation$startTim, _calculation$uuid;
|
|
2978
|
+
const calculation = this._calculations[domain].find(calculation => (calculation == null ? void 0 : calculation.uuid) === uuid);
|
|
2979
|
+
if (calculation) {
|
|
2980
|
+
calculation.endTimestamp = Date.now();
|
|
2981
|
+
calculation.isFiled = isFailed;
|
|
2982
|
+
}
|
|
2983
|
+
const elapsed = ((((_calculation$endTimes = calculation == null ? void 0 : calculation.endTimestamp) != null ? _calculation$endTimes : 0) - ((_calculation$startTim = calculation == null ? void 0 : calculation.startTimestamp) != null ? _calculation$startTim : 0)) / 1000).toFixed(1);
|
|
2984
|
+
Logger.log("endCalculation", `${elapsed} ms: ${domain}.${((_calculation$uuid = calculation == null ? void 0 : calculation.uuid) != null ? _calculation$uuid : "").slice(0, 7)}`);
|
|
2985
|
+
return calculation;
|
|
2986
|
+
} catch (e) {
|
|
2987
|
+
Logger.logError(e, "endCalculation");
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
isCalculationLate(domain, uuid) {
|
|
2991
|
+
const queue = this._calculations[domain];
|
|
2992
|
+
const analysingCalculation = queue.find(item => item.uuid === uuid);
|
|
2993
|
+
return analysingCalculation && !!queue.find(calculation => calculation.endTimestamp != null && calculation.startTimestamp > analysingCalculation.startTimestamp);
|
|
2994
|
+
}
|
|
2995
|
+
printCalculationsWaitingMoreThanSpecifiedSeconds(waitingLastsMs = 2000) {
|
|
2996
|
+
const calculations = Object.keys(this._calculations).map(domain => this._calculations[domain].map(c => _extends({}, c, {
|
|
2997
|
+
domain
|
|
2998
|
+
}))).flat().filter(c => c.endTimestamp === null && Date.now() - c.startTimestamp > waitingLastsMs);
|
|
2999
|
+
Logger.log("printCalculationsWaitingMoreThanSpecifiedSeconds", `Calculations waiting more than ${(waitingLastsMs / 1000).toFixed(1)}s:\n` + calculations.map(c => `${c.domain}.${c.uuid.slice(0, 8)}: ${Date.now() - c.startTimestamp}\n`));
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
const concurrentCalculationsMetadataHolder = new ConcurrentCalculationsMetadataHolder();
|
|
3003
|
+
|
|
3004
|
+
class ExternalServicesStatsCollector {
|
|
3005
|
+
constructor() {
|
|
3006
|
+
this.stats = new Map();
|
|
3007
|
+
}
|
|
3008
|
+
externalServiceFailed(serviceUrl, message) {
|
|
3009
|
+
try {
|
|
3010
|
+
const processMessage = (stat, errorMessage) => {
|
|
3011
|
+
var _stat$errors, _errorMessage;
|
|
3012
|
+
const errors = (_stat$errors = stat.errors) != null ? _stat$errors : {};
|
|
3013
|
+
errorMessage = (_errorMessage = errorMessage) != null ? _errorMessage : "";
|
|
3014
|
+
if (errorMessage.match(/.*network.+error.*/i)) {
|
|
3015
|
+
errors["networkError"] = (errors["networkError"] || 0) + 1;
|
|
3016
|
+
} else if (errorMessage.match(/.*timeout.+exceeded.*/i)) {
|
|
3017
|
+
errors["timeoutExceeded"] = (errors["timeoutExceeded"] || 0) + 1;
|
|
3018
|
+
} else if (errors["other"]) {
|
|
3019
|
+
errors["other"].push(message);
|
|
3020
|
+
} else {
|
|
3021
|
+
errors["other"] = [message];
|
|
3022
|
+
}
|
|
3023
|
+
stat.errors = errors;
|
|
3024
|
+
};
|
|
3025
|
+
if (this.stats.has(serviceUrl)) {
|
|
3026
|
+
const stat = this.stats.get(serviceUrl);
|
|
3027
|
+
stat.callsCount += 1;
|
|
3028
|
+
stat.failsCount += 1;
|
|
3029
|
+
processMessage(stat, message);
|
|
3030
|
+
} else {
|
|
3031
|
+
this.stats.set(serviceUrl, {
|
|
3032
|
+
callsCount: 1,
|
|
3033
|
+
failsCount: 1
|
|
3034
|
+
});
|
|
3035
|
+
processMessage(this.stats.get(serviceUrl), message);
|
|
3036
|
+
}
|
|
3037
|
+
} catch (e) {
|
|
3038
|
+
improveAndRethrow(e, "externalServiceFailed");
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
externalServiceCalledWithoutError(serviceUrl) {
|
|
3042
|
+
try {
|
|
3043
|
+
if (this.stats.has(serviceUrl)) {
|
|
3044
|
+
const stat = this.stats.get(serviceUrl);
|
|
3045
|
+
stat.callsCount += 1;
|
|
3046
|
+
} else {
|
|
3047
|
+
this.stats.set(serviceUrl, {
|
|
3048
|
+
callsCount: 1,
|
|
3049
|
+
failsCount: 0
|
|
3050
|
+
});
|
|
3051
|
+
}
|
|
3052
|
+
} catch (e) {
|
|
3053
|
+
improveAndRethrow(e, "externalServiceCalledWithoutError");
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
|
|
3057
|
+
/**
|
|
3058
|
+
* Returns statistics about external services failures.
|
|
3059
|
+
* Provides how many calls were performed and what the percent of failed calls. Also returns errors stat.
|
|
3060
|
+
*
|
|
3061
|
+
* @return {Array<object>} Array of objects of type { failsPerCent: number, calls: number }
|
|
3062
|
+
* sorted by the highest fails percent desc
|
|
3063
|
+
*/
|
|
3064
|
+
getStats() {
|
|
3065
|
+
try {
|
|
3066
|
+
return Array.from(this.stats.keys()).map(key => {
|
|
3067
|
+
var _stat$errors2;
|
|
3068
|
+
const stat = this.stats.get(key);
|
|
3069
|
+
return {
|
|
3070
|
+
url: key,
|
|
3071
|
+
failsPerCent: (stat.failsCount / stat.callsCount * 100).toFixed(2),
|
|
3072
|
+
calls: stat.callsCount,
|
|
3073
|
+
errors: (_stat$errors2 = stat.errors) != null ? _stat$errors2 : []
|
|
3074
|
+
};
|
|
3075
|
+
}).sort((s1, s2) => s1.failsPerCent - s2.failsPerCent);
|
|
3076
|
+
} catch (e) {
|
|
3077
|
+
Logger.logError(e, "getStats");
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
|
|
3082
|
+
/**
|
|
3083
|
+
* TODO: [refactoring, critical] update backend copy of this service. Also there is a task to extract this
|
|
3084
|
+
* service and other related to it stuff to dedicated npm package task_id=b008ee5e4a3f42c08c73831c4bb3db4e
|
|
3085
|
+
*
|
|
3086
|
+
* Template service needed to avoid duplication of the same logic when we need to call
|
|
3087
|
+
* external APIs to retrieve some data. The idea is to use several API providers to retrieve the same data. It helps to
|
|
3088
|
+
* improve the reliability of a data retrieval.
|
|
3089
|
+
*/
|
|
3090
|
+
class RobustExternalAPICallerService {
|
|
3091
|
+
static getStats() {
|
|
3092
|
+
this.statsCollector.getStats();
|
|
3093
|
+
}
|
|
3094
|
+
|
|
3095
|
+
/**
|
|
3096
|
+
* @param bio {string} service name for logging
|
|
3097
|
+
* @param providersData {ExternalApiProvider[]} array of providers
|
|
3098
|
+
* @param [logger] {function} function to be used for logging
|
|
3099
|
+
*/
|
|
3100
|
+
constructor(bio, providersData, logger = Logger.logError) {
|
|
3101
|
+
providersData.forEach(provider => {
|
|
3102
|
+
if (!provider.endpoint && provider.endpoint !== "" || !provider.httpMethod) {
|
|
3103
|
+
throw new Error(`Wrong format of providers data for: ${JSON.stringify(provider)}`);
|
|
3104
|
+
}
|
|
3105
|
+
});
|
|
3106
|
+
|
|
3107
|
+
// We add niceFactor - just number to order the providers array by. It is helpful to call
|
|
3108
|
+
// less robust APIs only if more robust fails
|
|
3109
|
+
this.providers = providersData;
|
|
3110
|
+
providersData.forEach(provider => provider.resetNiceFactor());
|
|
3111
|
+
this.bio = bio;
|
|
3112
|
+
this._logger = Logger.logError;
|
|
3113
|
+
}
|
|
3114
|
+
/**
|
|
3115
|
+
* Performs data retrieval from external APIs. Tries providers till the data is retrieved.
|
|
3116
|
+
*
|
|
3117
|
+
* @param parametersValues {array} array of values of the parameters for URL query string [and/or body]
|
|
3118
|
+
* @param timeoutMS {number} http timeout to wait for response. If provider has its specific timeout value then it is used
|
|
3119
|
+
* @param [cancelToken] {object|undefined} axios token to force-cancel requests from high-level code
|
|
3120
|
+
* @param [attemptsCount] {number|undefined} number of attempts to be performed
|
|
3121
|
+
* @param [doNotFailForNowData] {boolean|undefined} pass true if you do not want us to throw an error if we retrieved null data from all the providers
|
|
3122
|
+
* @return {Promise<any>} resolving to retrieved data (or array of results if specific provider requires
|
|
3123
|
+
* several requests. NOTE: we flatten nested arrays - results of each separate request done for the specific provider)
|
|
3124
|
+
* @throws Error if requests to all providers are failed
|
|
3125
|
+
*/
|
|
3126
|
+
async callExternalAPI(parametersValues = [], timeoutMS = 3500, cancelToken = null, attemptsCount = 1, doNotFailForNowData = false) {
|
|
3127
|
+
var _this = this;
|
|
3128
|
+
let result;
|
|
3129
|
+
const calculationUuid = concurrentCalculationsMetadataHolder.startCalculation(this.bio);
|
|
3130
|
+
try {
|
|
3131
|
+
var _result4, _result5;
|
|
3132
|
+
for (let i = 0; (i < attemptsCount || (_result = result) != null && _result.shouldBeForceRetried) && ((_result2 = result) == null ? void 0 : _result2.data) == null; ++i) {
|
|
3133
|
+
var _result, _result2;
|
|
3134
|
+
/**
|
|
3135
|
+
* We use rpsFactor to improve re-attempting to call the providers if the last attempt resulted with
|
|
3136
|
+
* the fail due to abused RPSes of some (most part of) providers.
|
|
3137
|
+
* The _performCallAttempt in such a case will return increased rpsFactor inside the result object.
|
|
3138
|
+
*/
|
|
3139
|
+
const rpsFactor = result ? result.rpsFactor : RobustExternalAPICallerService.defaultRPSFactor;
|
|
3140
|
+
result = null;
|
|
3141
|
+
try {
|
|
3142
|
+
var _result3, _result$errors;
|
|
3143
|
+
if (i === 0 && !((_result3 = result) != null && _result3.shouldBeForceRetried)) {
|
|
3144
|
+
result = await this._performCallAttempt(parametersValues, timeoutMS, cancelToken, rpsFactor, doNotFailForNowData);
|
|
3145
|
+
} else {
|
|
3146
|
+
const maxRps = Math.max(...this.providers.map(provider => {
|
|
3147
|
+
var _provider$getRps;
|
|
3148
|
+
return (_provider$getRps = provider.getRps()) != null ? _provider$getRps : 0;
|
|
3149
|
+
}));
|
|
3150
|
+
const waitingTimeMs = maxRps ? 1000 / (maxRps / rpsFactor) : 0;
|
|
3151
|
+
result = await new Promise((resolve, reject) => {
|
|
3152
|
+
setTimeout(async function () {
|
|
3153
|
+
try {
|
|
3154
|
+
resolve(await _this._performCallAttempt(parametersValues, timeoutMS, cancelToken, rpsFactor, doNotFailForNowData));
|
|
3155
|
+
} catch (e) {
|
|
3156
|
+
reject(e);
|
|
3157
|
+
}
|
|
3158
|
+
}, waitingTimeMs);
|
|
3159
|
+
});
|
|
3160
|
+
}
|
|
3161
|
+
if ((_result$errors = result.errors) != null && _result$errors.length) {
|
|
3162
|
+
const errors = result.errors;
|
|
3163
|
+
this._logger(new Error(`Failed at attempt ${i}. ${errors.length} errors. Messages: ${safeStringify(errors.map(error => error.message))}: ${safeStringify(errors)}.`), `${this.bio}.callExternalAPI`, "", true);
|
|
3164
|
+
}
|
|
3165
|
+
} catch (e) {
|
|
3166
|
+
this._logger(e, `${this.bio}.callExternalAPI`, "Failed to perform external providers calling");
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
if (((_result4 = result) == null ? void 0 : _result4.data) == null) {
|
|
3170
|
+
// TODO: [feature, moderate] looks like we should not fail for null data as it is strange - the provider will fail when processing data internally
|
|
3171
|
+
const error = new Error(`Failed to retrieve data. It means all attempts have been failed. DEV: add more attempts to this data retrieval`);
|
|
3172
|
+
if (!doNotFailForNowData) {
|
|
3173
|
+
throw error;
|
|
3174
|
+
} else {
|
|
3175
|
+
this._logger(error, `${this.bio}.callExternalAPI`);
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
return (_result5 = result) == null ? void 0 : _result5.data;
|
|
3179
|
+
} catch (e) {
|
|
3180
|
+
improveAndRethrow(e, `${this.bio}.callExternalAPI`);
|
|
3181
|
+
} finally {
|
|
3182
|
+
concurrentCalculationsMetadataHolder.endCalculation(this.bio, calculationUuid);
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
async _performCallAttempt(parametersValues, timeoutMS, cancelToken, rpsFactor, doNotFailForNowData) {
|
|
3186
|
+
var _data;
|
|
3187
|
+
const providers = this._reorderProvidersByNiceFactor();
|
|
3188
|
+
let data = undefined,
|
|
3189
|
+
providerIndex = 0,
|
|
3190
|
+
countOfRequestsDeclinedByRps = 0,
|
|
3191
|
+
errors = [];
|
|
3192
|
+
while (!data && providerIndex < providers.length) {
|
|
3193
|
+
let provider = providers[providerIndex];
|
|
3194
|
+
if (provider.isRpsExceeded()) {
|
|
3195
|
+
/**
|
|
3196
|
+
* Current provider's RPS is exceeded, so we try next provider. Also, we count such cases to make
|
|
3197
|
+
* a decision about the force-retry need.
|
|
3198
|
+
*/
|
|
3199
|
+
++providerIndex;
|
|
3200
|
+
++countOfRequestsDeclinedByRps;
|
|
3201
|
+
continue;
|
|
3202
|
+
}
|
|
3203
|
+
try {
|
|
3204
|
+
var _provider$specificHea;
|
|
3205
|
+
const axiosConfig = _extends({}, cancelToken ? {
|
|
3206
|
+
cancelToken
|
|
3207
|
+
} : {}, {
|
|
3208
|
+
timeout: provider.timeout || timeoutMS,
|
|
3209
|
+
headers: (_provider$specificHea = provider.specificHeaders) != null ? _provider$specificHea : {}
|
|
3210
|
+
});
|
|
3211
|
+
const httpMethods = Array.isArray(provider.httpMethod) ? provider.httpMethod : [provider.httpMethod];
|
|
3212
|
+
const iterationsData = [];
|
|
3213
|
+
for (let subRequestIndex = 0; subRequestIndex < httpMethods.length; ++subRequestIndex) {
|
|
3214
|
+
const query = provider.composeQueryString(parametersValues, subRequestIndex);
|
|
3215
|
+
const endpoint = `${provider.endpoint}${query}`;
|
|
3216
|
+
const axiosParams = [endpoint, axiosConfig];
|
|
3217
|
+
if (["post", "put", "patch"].find(method => method === httpMethods[subRequestIndex])) {
|
|
3218
|
+
var _provider$composeBody;
|
|
3219
|
+
const body = (_provider$composeBody = provider.composeBody(parametersValues, subRequestIndex)) != null ? _provider$composeBody : null;
|
|
3220
|
+
axiosParams.splice(1, 0, body);
|
|
3221
|
+
}
|
|
3222
|
+
let pageNumber = 0;
|
|
3223
|
+
const responsesForPages = [];
|
|
3224
|
+
let hasNextPage = provider.doesSupportPagination();
|
|
3225
|
+
do {
|
|
3226
|
+
if (subRequestIndex === 0 && pageNumber === 0) {
|
|
3227
|
+
provider.actualizeLastCalledTimestamp();
|
|
3228
|
+
responsesForPages[pageNumber] = await AxiosAdapter.call(httpMethods[subRequestIndex], ...axiosParams);
|
|
3229
|
+
RobustExternalAPICallerService.statsCollector.externalServiceCalledWithoutError(provider.getApiGroupId());
|
|
3230
|
+
} else {
|
|
3231
|
+
if (pageNumber > 0) {
|
|
3232
|
+
const actualizedParams = provider.changeQueryParametersForPageNumber(parametersValues, responsesForPages[pageNumber - 1], pageNumber, subRequestIndex);
|
|
3233
|
+
const _query = provider.composeQueryString(actualizedParams, subRequestIndex);
|
|
3234
|
+
axiosParams[0] = `${provider.endpoint}${_query}`;
|
|
3235
|
+
}
|
|
3236
|
+
/**
|
|
3237
|
+
* For second and more request we postpone each request to not exceed RPS
|
|
3238
|
+
* of current provider. We use rpsFactor to dynamically increase the rps to avoid
|
|
3239
|
+
* too frequent calls if we continue failing to retrieve the data due to RPS exceeding.
|
|
3240
|
+
* TODO: [dev] test RPS factor logic (units or integration)
|
|
3241
|
+
*/
|
|
3242
|
+
|
|
3243
|
+
const waitingTimeMS = provider.getRps() ? 1000 / (provider.getRps() / rpsFactor) : 0;
|
|
3244
|
+
const postponeUntilRpsExceeded = async function postponeUntilRpsExceeded(recursionLevel = 0) {
|
|
3245
|
+
return await postponeExecution(async function () {
|
|
3246
|
+
const maxCountOfPostponingAttempts = 2;
|
|
3247
|
+
if (provider.isRpsExceeded() && recursionLevel < maxCountOfPostponingAttempts) {
|
|
3248
|
+
return await postponeUntilRpsExceeded(recursionLevel + 1);
|
|
3249
|
+
}
|
|
3250
|
+
provider.actualizeLastCalledTimestamp();
|
|
3251
|
+
return await AxiosAdapter.call(httpMethods[subRequestIndex], ...axiosParams);
|
|
3252
|
+
}, waitingTimeMS);
|
|
3253
|
+
};
|
|
3254
|
+
responsesForPages[pageNumber] = await postponeUntilRpsExceeded();
|
|
3255
|
+
}
|
|
3256
|
+
if (hasNextPage) {
|
|
3257
|
+
hasNextPage = !provider.checkWhetherResponseIsForLastPage(responsesForPages[pageNumber - 1], responsesForPages[pageNumber], pageNumber, subRequestIndex);
|
|
3258
|
+
}
|
|
3259
|
+
pageNumber++;
|
|
3260
|
+
} while (hasNextPage);
|
|
3261
|
+
const responsesDataForPages = responsesForPages.map(response => provider.getDataByResponse(response, parametersValues, subRequestIndex, iterationsData));
|
|
3262
|
+
let allData = responsesDataForPages;
|
|
3263
|
+
if (Array.isArray(responsesDataForPages[0])) {
|
|
3264
|
+
allData = responsesDataForPages.flat();
|
|
3265
|
+
} else if (responsesDataForPages.length === 1) {
|
|
3266
|
+
allData = responsesDataForPages[0];
|
|
3267
|
+
}
|
|
3268
|
+
iterationsData.push(allData);
|
|
3269
|
+
}
|
|
3270
|
+
if (iterationsData.length) {
|
|
3271
|
+
if (httpMethods.length > 1) {
|
|
3272
|
+
data = provider.incorporateIterationsData(iterationsData);
|
|
3273
|
+
} else {
|
|
3274
|
+
data = iterationsData[0];
|
|
3275
|
+
}
|
|
3276
|
+
} else if (!doNotFailForNowData) {
|
|
3277
|
+
RobustExternalAPICallerService.statsCollector.externalServiceFailed(provider.getApiGroupId(), "Response data was null for some reason");
|
|
3278
|
+
punishProvider(provider);
|
|
3279
|
+
}
|
|
3280
|
+
} catch (e) {
|
|
3281
|
+
punishProvider(provider);
|
|
3282
|
+
RobustExternalAPICallerService.statsCollector.externalServiceFailed(provider.getApiGroupId(), e == null ? void 0 : e.message);
|
|
3283
|
+
errors.push(e);
|
|
3284
|
+
} finally {
|
|
3285
|
+
providerIndex++;
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
|
|
3289
|
+
// If we are declining more than 50% of providers (by exceeding RPS) then we note that it better to retry the whole process of providers requesting
|
|
3290
|
+
const shouldBeForceRetried = data == null && countOfRequestsDeclinedByRps > Math.floor(providers.length * 0.5);
|
|
3291
|
+
const rpsMultiplier = shouldBeForceRetried ? RobustExternalAPICallerService.rpsMultiplier : 1;
|
|
3292
|
+
return {
|
|
3293
|
+
data: (_data = data) != null ? _data : null,
|
|
3294
|
+
shouldBeForceRetried,
|
|
3295
|
+
rpsFactor: rpsFactor * rpsMultiplier,
|
|
3296
|
+
errors
|
|
3297
|
+
};
|
|
3298
|
+
}
|
|
3299
|
+
_reorderProvidersByNiceFactor() {
|
|
3300
|
+
const providersCopy = [...this.providers];
|
|
3301
|
+
return providersCopy.sort((p1, p2) => p2.niceFactor - p1.niceFactor);
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
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
|
+
}
|
|
3310
|
+
|
|
3311
|
+
/**
|
|
3312
|
+
* Extended edit of RobustExternalApiCallerService supporting cache and management of concurrent requests
|
|
3313
|
+
* to the same resource.
|
|
3314
|
+
* TODO: [tests, critical] Massively used logic
|
|
3315
|
+
*/
|
|
3316
|
+
class CachedRobustExternalApiCallerService {
|
|
3317
|
+
/**
|
|
3318
|
+
* @param bio {string} unique service identifier
|
|
3319
|
+
* @param cache {Cache} cache instance
|
|
3320
|
+
* @param providersData {ExternalApiProvider[]} array of providers
|
|
3321
|
+
* @param [cacheTtlMs=10000] {number} time to live for cache ms
|
|
3322
|
+
* @param [maxCallAttemptsToWaitForAlreadyRunningRequest=50] {number} see details in CacheAndConcurrentRequestsResolver
|
|
3323
|
+
* @param [timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished=3000] {number} see details in CacheAndConcurrentRequestsResolver
|
|
3324
|
+
* @param [removeExpiredCacheAutomatically=true] {boolean} whether to remove cached data automatically when ttl exceeds
|
|
3325
|
+
* @param [mergeCachedAndNewlyRetrievedData=null] {function} function accepting cached data, newly retrieved data and id field name for list items
|
|
3326
|
+
* and merging them. use if needed
|
|
3327
|
+
*/
|
|
3328
|
+
constructor(bio, cache, providersData, cacheTtlMs = 10000, removeExpiredCacheAutomatically = true, mergeCachedAndNewlyRetrievedData = null, maxCallAttemptsToWaitForAlreadyRunningRequest = 100, timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished = 1000) {
|
|
3329
|
+
this._provider = new RobustExternalAPICallerService(`cached_${bio}`, providersData, Logger.logError);
|
|
3330
|
+
this._cacheTtlMs = cacheTtlMs;
|
|
3331
|
+
this._cahceAndRequestsResolver = new CacheAndConcurrentRequestsResolver(bio, cache, cacheTtlMs, removeExpiredCacheAutomatically, maxCallAttemptsToWaitForAlreadyRunningRequest, timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished);
|
|
3332
|
+
this._cahceIds = [];
|
|
3333
|
+
this._mergeCachedAndNewlyRetrievedData = mergeCachedAndNewlyRetrievedData;
|
|
3334
|
+
}
|
|
3335
|
+
|
|
3336
|
+
/**
|
|
3337
|
+
* Calls the external API or returns data from cache. Just waits if the same data already requested.
|
|
3338
|
+
*
|
|
3339
|
+
* @param parametersValues {array} array of values of the parameters for URL query string [and/or body]
|
|
3340
|
+
* @param timeoutMS {number} http timeout to wait for response. If provider has its specific timeout value then it is used
|
|
3341
|
+
* @param [cancelToken] {object|undefined} axios token to force-cancel requests from high-level code
|
|
3342
|
+
* @param [attemptsCount] {number|undefined} number of attempts to be performed
|
|
3343
|
+
* @param [customHashFunctionForParams] {function|undefined} function without params calculating the hash to be
|
|
3344
|
+
* added to bio of the service to compose a unique parameters-specific cache id
|
|
3345
|
+
* @param [doNotFailForNowData] {boolean|undefined} pass true if you do not want us to throw an error if we retrieved null data from all the providers
|
|
3346
|
+
* @return {Promise<any>} resolving to retrieved data (or array of results if specific provider requires
|
|
3347
|
+
* several requests. NOTE: we flatten nested arrays - results of each separate request done for the specific provider)
|
|
3348
|
+
* @throws Error if requests to all providers are failed
|
|
3349
|
+
*/
|
|
3350
|
+
async callExternalAPICached(parametersValues = [], timeoutMS = 3500, cancelToken = null, attemptsCount = 1, customHashFunctionForParams = null, doNotFailForNowData = false) {
|
|
3351
|
+
const loggerSource = `${this._provider.bio}.callExternalAPICached`;
|
|
3352
|
+
let cacheId;
|
|
3353
|
+
let result;
|
|
3354
|
+
try {
|
|
3355
|
+
var _result;
|
|
3356
|
+
cacheId = this._calculateCacheId(parametersValues, customHashFunctionForParams);
|
|
3357
|
+
result = await this._cahceAndRequestsResolver.getCachedOrWaitForCachedOrAcquireLock(cacheId);
|
|
3358
|
+
if (!((_result = result) != null && _result.canStartDataRetrieval)) {
|
|
3359
|
+
var _result2;
|
|
3360
|
+
return (_result2 = result) == null ? void 0 : _result2.cachedData;
|
|
3361
|
+
}
|
|
3362
|
+
let data = await this._provider.callExternalAPI(parametersValues, timeoutMS, cancelToken, attemptsCount, doNotFailForNowData);
|
|
3363
|
+
const canPerformMerge = typeof this._mergeCachedAndNewlyRetrievedData === "function";
|
|
3364
|
+
if (canPerformMerge) {
|
|
3365
|
+
const mostRecentCached = this._cahceAndRequestsResolver.getCached(cacheId);
|
|
3366
|
+
data = this._mergeCachedAndNewlyRetrievedData(mostRecentCached, data, parametersValues);
|
|
3367
|
+
}
|
|
3368
|
+
if (data != null) {
|
|
3369
|
+
var _result3;
|
|
3370
|
+
this._cahceAndRequestsResolver.saveCachedData(cacheId, (_result3 = result) == null ? void 0 : _result3.lockId, data, true, canPerformMerge);
|
|
3371
|
+
this._cahceIds.indexOf(cacheId) < 0 && this._cahceIds.push(cacheId);
|
|
3372
|
+
}
|
|
3373
|
+
return data;
|
|
3374
|
+
} catch (e) {
|
|
3375
|
+
improveAndRethrow(e, loggerSource);
|
|
3376
|
+
} finally {
|
|
3377
|
+
var _result4;
|
|
3378
|
+
this._cahceAndRequestsResolver.releaseLock(cacheId, (_result4 = result) == null ? void 0 : _result4.lockId);
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
invalidateCaches() {
|
|
3382
|
+
this._cahceIds.forEach(key => this._cahceAndRequestsResolver.invalidate(key));
|
|
3383
|
+
}
|
|
3384
|
+
actualizeCachedData(params, synchronousCurrentCacheProcessor, customHashFunctionForParams = null, sessionDependent = true, actualizedAtTimestamp) {
|
|
3385
|
+
const cacheId = this._calculateCacheId(params, customHashFunctionForParams);
|
|
3386
|
+
this._cahceAndRequestsResolver.actualizeCachedData(cacheId, synchronousCurrentCacheProcessor, sessionDependent);
|
|
3387
|
+
}
|
|
3388
|
+
markCacheAsExpiredButDontRemove(parametersValues, customHashFunctionForParams) {
|
|
3389
|
+
try {
|
|
3390
|
+
this._cahceAndRequestsResolver.markAsExpiredButDontRemove(this._calculateCacheId(parametersValues, customHashFunctionForParams));
|
|
3391
|
+
} catch (e) {
|
|
3392
|
+
improveAndRethrow(e, "markCacheAsExpiredButDontRemove");
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
_calculateCacheId(parametersValues, customHashFunctionForParams = null) {
|
|
3396
|
+
try {
|
|
3397
|
+
const hash = typeof customHashFunctionForParams === "function" ? customHashFunctionForParams(parametersValues) : !parametersValues ? "" : new Hashes.SHA512().hex(safeStringify(parametersValues));
|
|
3398
|
+
return `${this._provider.bio}-${hash}`;
|
|
3399
|
+
} catch (e) {
|
|
3400
|
+
improveAndRethrow(e, this._provider.bio + "_calculateCacheId");
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
|
|
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 {
|
|
3410
|
+
constructor() {
|
|
3411
|
+
this._cancelToken = axios.CancelToken.source();
|
|
3412
|
+
this._isCanceled = false;
|
|
3413
|
+
}
|
|
3414
|
+
cancel() {
|
|
3415
|
+
this._isCanceled = true;
|
|
3416
|
+
this._cancelToken.cancel();
|
|
3417
|
+
}
|
|
3418
|
+
isCanceled() {
|
|
3419
|
+
return this._isCanceled;
|
|
3420
|
+
}
|
|
3421
|
+
getToken() {
|
|
3422
|
+
return this._cancelToken.token;
|
|
3423
|
+
}
|
|
3424
|
+
static instance() {
|
|
3425
|
+
return new CancelProcessing();
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3429
|
+
class ExternalApiProvider {
|
|
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();
|
|
3467
|
+
}
|
|
3468
|
+
actualizeLastCalledTimestamp() {
|
|
3469
|
+
this.apiGroup.actualizeLastCalledTimestamp();
|
|
3470
|
+
}
|
|
3471
|
+
getApiGroupId() {
|
|
3472
|
+
return this.apiGroup.id;
|
|
3473
|
+
}
|
|
3474
|
+
|
|
3475
|
+
/**
|
|
3476
|
+
* Some endpoint can require several sub requests. Example is one request to get confirmed transactions
|
|
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;
|
|
3483
|
+
}
|
|
3484
|
+
|
|
3485
|
+
/**
|
|
3486
|
+
* Some endpoint support pagination. Override this method if so and implement corresponding methods.
|
|
3487
|
+
*
|
|
3488
|
+
* @return {boolean} true if this provider requires several requests to retrieve the data
|
|
3489
|
+
*/
|
|
3490
|
+
doesSupportPagination() {
|
|
3491
|
+
return false;
|
|
3492
|
+
}
|
|
3493
|
+
|
|
3494
|
+
/**
|
|
3495
|
+
* Composes a query string to be added to the endpoint of this provider.
|
|
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 "";
|
|
3503
|
+
}
|
|
3504
|
+
|
|
3505
|
+
/**
|
|
3506
|
+
* Composes a body to be added to the request
|
|
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 "";
|
|
3514
|
+
}
|
|
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
|
+
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;
|
|
3542
|
+
}
|
|
3543
|
+
|
|
3544
|
+
/**
|
|
3545
|
+
* Function checking whether the response is for the last page to stop requesting for a next page.
|
|
3546
|
+
* Only for endpoints supporting pagination.
|
|
3547
|
+
*
|
|
3548
|
+
* @param previousResponse {Object} HTTP response returned by provider for previous call (previous page)
|
|
3549
|
+
* @param currentResponse {Object} HTTP response returned by provider for current call (current page, next after the previous)
|
|
3550
|
+
* @param currentPageNumber {number} current page number (for current response)
|
|
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.
|
|
3567
|
+
*
|
|
3568
|
+
* @param iterationsData {any[]} iterations data retrieved from getDataByResponse called per sub-request.
|
|
3569
|
+
* @return {any} by default flatten the passed iterations data array. Should be redefined if you need another logic.
|
|
3570
|
+
*/
|
|
3571
|
+
incorporateIterationsData(iterationsData) {
|
|
3572
|
+
return iterationsData.flat();
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
|
|
3576
|
+
/**
|
|
3577
|
+
* Models a group of APIs provided by the same owner and used for different services in our app.
|
|
3578
|
+
* It means we need to mention RPS several times for each usage and also have some holder of last call timestamp per
|
|
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.
|
|
3581
|
+
*/
|
|
3582
|
+
class ApiGroup {
|
|
3583
|
+
constructor(id, rps, backendProxyIdGenerator = null) {
|
|
3584
|
+
this.id = id;
|
|
3585
|
+
this.rps = rps;
|
|
3586
|
+
this.lastCalledTimestamp = null;
|
|
3587
|
+
this.backendProxyIdGenerator = backendProxyIdGenerator;
|
|
3588
|
+
}
|
|
3589
|
+
isRpsExceeded() {
|
|
3590
|
+
var _this$lastCalledTimes;
|
|
3591
|
+
return ((_this$lastCalledTimes = this.lastCalledTimestamp) != null ? _this$lastCalledTimes : 0) + Math.floor(1000 / this.rps) > Date.now();
|
|
3592
|
+
}
|
|
3593
|
+
actualizeLastCalledTimestamp() {
|
|
3594
|
+
this.lastCalledTimestamp = Date.now();
|
|
3595
|
+
}
|
|
3596
|
+
}
|
|
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
|
+
|
|
3644
|
+
class ExistingSwap {
|
|
3645
|
+
/**
|
|
3646
|
+
* @param swapId {string}
|
|
3647
|
+
* @param status {SwapProvider.SWAP_STATUSES}
|
|
3648
|
+
* @param createdAt {number}
|
|
3649
|
+
* @param expiresAt {number}
|
|
3650
|
+
* @param confirmations {number}
|
|
3651
|
+
* @param rate {string}
|
|
3652
|
+
* @param refundAddress {string}
|
|
3653
|
+
* @param payToAddress {string}
|
|
3654
|
+
* @param fromCoin {Coin}
|
|
3655
|
+
* @param fromAmount {string}
|
|
3656
|
+
* @param fromTransactionId {string}
|
|
3657
|
+
* @param fromTransactionLink {string}
|
|
3658
|
+
* @param toCoin {Coin}
|
|
3659
|
+
* @param toAmount {string}
|
|
3660
|
+
* @param toTransactionId {string|null}
|
|
3661
|
+
* @param toTransactionLink {string}
|
|
3662
|
+
* @param toAddress {string}
|
|
3663
|
+
* @param partner {string}
|
|
3664
|
+
* @param fromExtraId {string}
|
|
3665
|
+
* @param toExtraId {string}
|
|
3666
|
+
* @param refundExtraId {string}
|
|
3667
|
+
*/
|
|
3668
|
+
constructor(swapId, status, createdAt, expiresAt, confirmations, rate, refundAddress, payToAddress, fromCoin, fromAmount, fromTransactionId, fromTransactionLink, toCoin, toAmount, toTransactionId, toTransactionLink, toAddress,
|
|
3669
|
+
// TODO: [refactoring, moderate] toAddress is not quite clear. How about recipientAddress? task_id=0815a111c99543b78d374217eadbde4f
|
|
3670
|
+
partner, fromExtraId, toExtraId, refundExtraId) {
|
|
3671
|
+
this.swapId = swapId;
|
|
3672
|
+
this.status = status;
|
|
3673
|
+
this.createdAt = createdAt;
|
|
3674
|
+
this.expiresAt = expiresAt;
|
|
3675
|
+
this.confirmations = confirmations;
|
|
3676
|
+
this.rate = rate;
|
|
3677
|
+
this.refundAddress = refundAddress;
|
|
3678
|
+
this.payToAddress = payToAddress;
|
|
3679
|
+
this.fromCoin = fromCoin;
|
|
3680
|
+
this.fromTransactionId = fromTransactionId;
|
|
3681
|
+
this.fromAmount = fromAmount;
|
|
3682
|
+
this.fromTransactionLink = fromTransactionLink;
|
|
3683
|
+
this.toCoin = toCoin;
|
|
3684
|
+
this.toTransactionId = toTransactionId;
|
|
3685
|
+
this.toTransactionLink = toTransactionLink;
|
|
3686
|
+
this.toAmount = toAmount;
|
|
3687
|
+
this.toAddress = toAddress;
|
|
3688
|
+
this.partner = partner;
|
|
3689
|
+
this.fromExtraId = fromExtraId;
|
|
3690
|
+
this.toExtraId = toExtraId;
|
|
3691
|
+
this.refundExtraId = refundExtraId;
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3694
|
+
|
|
3695
|
+
class ExistingSwapWithFiatData extends ExistingSwap {
|
|
3696
|
+
/**
|
|
3697
|
+
* @param swapId {string}
|
|
3698
|
+
* @param status {SwapProvider.SWAP_STATUSES}
|
|
3699
|
+
* @param createdAt {number}
|
|
3700
|
+
* @param expiresAt {number}
|
|
3701
|
+
* @param confirmations {number}
|
|
3702
|
+
* @param rate {string}
|
|
3703
|
+
* @param refundAddress {string}
|
|
3704
|
+
* @param payToAddress {string}
|
|
3705
|
+
* @param fromCoin {Coin}
|
|
3706
|
+
* @param fromAmount {string}
|
|
3707
|
+
* @param fromTransactionId {string}
|
|
3708
|
+
* @param fromTransactionLink {string}
|
|
3709
|
+
* @param toCoin {Coin}
|
|
3710
|
+
* @param toAmount {string}
|
|
3711
|
+
* @param toTransactionId {string|null}
|
|
3712
|
+
* @param toTransactionLink
|
|
3713
|
+
* @param toAddress {string}
|
|
3714
|
+
* @param partner {string}
|
|
3715
|
+
* @param fromExtraId {string}
|
|
3716
|
+
* @param toExtraId {string}
|
|
3717
|
+
* @param refundExtraId {string}
|
|
3718
|
+
* @param fromAmountFiat {number}
|
|
3719
|
+
* @param toAmountFiat {number}
|
|
3720
|
+
* @param fiatCurrencyCode {string}
|
|
3721
|
+
* @param fiatCurrencyDecimals {number}
|
|
3722
|
+
*/
|
|
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);
|
|
3725
|
+
this.fromAmountFiat = fromAmountFiat;
|
|
3726
|
+
this.toAmountFiat = toAmountFiat;
|
|
3727
|
+
this.fiatCurrencyCode = fiatCurrencyCode;
|
|
3728
|
+
this.fiatCurrencyDecimals = fiatCurrencyDecimals;
|
|
3729
|
+
}
|
|
3730
|
+
|
|
3731
|
+
/**
|
|
3732
|
+
* @param existingSwap {ExistingSwap}
|
|
3733
|
+
* @param fromAmountFiat {number}
|
|
3734
|
+
* @param toAmountFiat {number}
|
|
3735
|
+
* @param fiatCurrencyCode {string}
|
|
3736
|
+
* @param fiatCurrencyDecimals {number}
|
|
3737
|
+
* @return {ExistingSwapWithFiatData}
|
|
3738
|
+
*/
|
|
3739
|
+
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);
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3743
|
+
|
|
3744
|
+
class BaseSwapCreationInfo {
|
|
3745
|
+
/**
|
|
3746
|
+
* @param fromCoin {Coin}
|
|
3747
|
+
* @param toCoin {Coin}
|
|
3748
|
+
* @param fromAmountCoins {string}
|
|
3749
|
+
* @param toAmountCoins {string}
|
|
3750
|
+
* @param rate {string}
|
|
3751
|
+
* @param rawSwapData {Object}
|
|
3752
|
+
* @param min {string}
|
|
3753
|
+
* @param fiatMin {number}
|
|
3754
|
+
* @param max {string}
|
|
3755
|
+
* @param fiatMax {number}
|
|
3756
|
+
* @param durationMinutesRange {string}
|
|
3757
|
+
*/
|
|
3758
|
+
constructor(fromCoin, toCoin, fromAmountCoins, toAmountCoins, rate, rawSwapData, min, fiatMin, max, fiatMax, durationMinutesRange) {
|
|
3759
|
+
this.fromCoin = fromCoin;
|
|
3760
|
+
this.toCoin = toCoin;
|
|
3761
|
+
this.fromAmountCoins = fromAmountCoins;
|
|
3762
|
+
this.toAmountCoins = toAmountCoins;
|
|
3763
|
+
this.rate = rate;
|
|
3764
|
+
this.rawSwapData = rawSwapData;
|
|
3765
|
+
this.min = min;
|
|
3766
|
+
this.fiatMin = fiatMin;
|
|
3767
|
+
this.max = max;
|
|
3768
|
+
this.fiatMax = fiatMax;
|
|
3769
|
+
this.durationMinutesRange = durationMinutesRange;
|
|
3770
|
+
}
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3773
|
+
class SwapProvider {
|
|
3774
|
+
/**
|
|
3775
|
+
* @return {Promise<void>}
|
|
3776
|
+
*/
|
|
3777
|
+
async initialize() {
|
|
3778
|
+
throw new Error("Not implemented in base");
|
|
3779
|
+
}
|
|
3780
|
+
|
|
3781
|
+
/**
|
|
3782
|
+
* @return {number} milliseconds TTL
|
|
3783
|
+
*/
|
|
3784
|
+
getSwapCreationInfoTtlMs() {
|
|
3785
|
+
throw new Error("Not implemented in base");
|
|
3786
|
+
}
|
|
3787
|
+
|
|
3788
|
+
/**
|
|
3789
|
+
* Retrieves all currencies supported by this swap provider.
|
|
3790
|
+
* Returns one of SwapProvider.COMMON_ERRORS in case of processable fail.
|
|
3791
|
+
*
|
|
3792
|
+
* @return {Promise<({ result: true, coins: Coin[] }|{ result: false, reason: string })>}
|
|
3793
|
+
*/
|
|
3794
|
+
async getAllSupportedCurrencies() {
|
|
3795
|
+
throw new Error("Not implemented in base");
|
|
3796
|
+
}
|
|
3797
|
+
|
|
3798
|
+
/**
|
|
3799
|
+
* Retrieves all deposit currencies supported by this swap provider.
|
|
3800
|
+
* Returns one of SwapProvider.COMMON_ERRORS in case of processable fail.
|
|
3801
|
+
*
|
|
3802
|
+
* @return {Promise<({ result: true, coins: Coin[] }|{ result: false, reason: string })>}
|
|
3803
|
+
*/
|
|
3804
|
+
async getDepositCurrencies() {
|
|
3805
|
+
throw new Error("Not implemented in base");
|
|
3806
|
+
}
|
|
3807
|
+
|
|
3808
|
+
/**
|
|
3809
|
+
* Retrieves all withdrawable currencies supported by this swap provider.
|
|
3810
|
+
* Returns one of SwapProvider.COMMON_ERRORS in case of processable fail.
|
|
3811
|
+
*
|
|
3812
|
+
* @param [exceptCurrency=null] {Coin|null}
|
|
3813
|
+
* @return {Promise<({ result: true, coins: Coin[] }|{ result: false, reason: string })>}
|
|
3814
|
+
*/
|
|
3815
|
+
async getWithdrawalCurrencies(exceptCurrency = null) {
|
|
3816
|
+
throw new Error("Not implemented in base");
|
|
3817
|
+
}
|
|
3818
|
+
|
|
3819
|
+
/**
|
|
3820
|
+
* Retrieves URL for coin icon or fallback if not found.
|
|
3821
|
+
*
|
|
3822
|
+
* @param coin {Coin|string} coin or rabbit-format of coin ticker
|
|
3823
|
+
* @return {string}
|
|
3824
|
+
*/
|
|
3825
|
+
getIconUrl(coin) {
|
|
3826
|
+
throw new Error("Not implemented in base");
|
|
3827
|
+
}
|
|
3828
|
+
|
|
3829
|
+
/**
|
|
3830
|
+
* Retrieves coin to USDT rate.
|
|
3831
|
+
*
|
|
3832
|
+
* @param coin {Coin}
|
|
3833
|
+
* @return {{result: true, rate: string}|{result: false}}
|
|
3834
|
+
*/
|
|
3835
|
+
async getCoinToUSDTRate(coin) {
|
|
3836
|
+
throw new Error("Not implemented in base");
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3839
|
+
/**
|
|
3840
|
+
* Retrieves estimation for swapping giving coins amount.
|
|
3841
|
+
* null min or max signals there is no corresponding limitation. undefined means that the limits were not retrieved.
|
|
3842
|
+
* For fail result on of SwapProvider.NO_SWAPS_REASONS or SwapProvider.COMMON_ERRORS reasons will be returned.
|
|
3843
|
+
*
|
|
3844
|
+
* WARNING: MUST return NOT_SUPPORTED error code for any case when pair is not available/supported (Should not throw random errors for this case)
|
|
3845
|
+
* @param fromCoin {Coin}
|
|
3846
|
+
* @param toCoin {Coin}
|
|
3847
|
+
* @param amountCoins {string}
|
|
3848
|
+
* @param [fromCoinToUsdRate=null] pass if you want to increase the min amount returned
|
|
3849
|
+
* by provider with some fixed "insurance" amount to cover min amount fluctuations.
|
|
3850
|
+
* @return {Promise<({
|
|
3851
|
+
* result: false,
|
|
3852
|
+
* reason: string,
|
|
3853
|
+
* smallestMin: (string|null|undefined),
|
|
3854
|
+
* greatestMax: (string|null|undefined),
|
|
3855
|
+
* }|{
|
|
3856
|
+
* result: true,
|
|
3857
|
+
* min: (string|null),
|
|
3858
|
+
* max: (string|null),
|
|
3859
|
+
* smallestMin: (string|null),
|
|
3860
|
+
* greatestMax: (string|null),
|
|
3861
|
+
* rate: (string|null),
|
|
3862
|
+
* durationMinutesRange: string,
|
|
3863
|
+
* [rawSwapData]: Object
|
|
3864
|
+
* })>}
|
|
3865
|
+
*/
|
|
3866
|
+
async getSwapInfo(fromCoin, toCoin, amountCoins, fromCoinToUsdRate = null) {
|
|
3867
|
+
throw new Error("Not implemented in base");
|
|
3868
|
+
}
|
|
3869
|
+
|
|
3870
|
+
/**
|
|
3871
|
+
* For fail result we return one of SwapProvider.CREATION_FAIL_REASONS or SwapProvider.COMMON_ERRORS.
|
|
3872
|
+
*
|
|
3873
|
+
* @param fromCoin {Coin}
|
|
3874
|
+
* @param toCoin {Coin}
|
|
3875
|
+
* @param amount {string}
|
|
3876
|
+
* @param toAddress {string}
|
|
3877
|
+
* @param refundAddress {string}
|
|
3878
|
+
* @param rawSwapData {Object|null}
|
|
3879
|
+
* @param clientIpAddress {string}
|
|
3880
|
+
* @param [toCurrencyExtraId=""] {string} optional extra ID
|
|
3881
|
+
* @param [refundExtraId=""] {string} optional extra ID for refund address
|
|
3882
|
+
* @return {Promise<({
|
|
3883
|
+
* result: true,
|
|
3884
|
+
* swapId: string,
|
|
3885
|
+
* fromCoin: Coin,
|
|
3886
|
+
* fromAmount: string,
|
|
3887
|
+
* fromAddress: string,
|
|
3888
|
+
* toCoin: Coin,
|
|
3889
|
+
* toAmount: string,
|
|
3890
|
+
* toAddress: string,
|
|
3891
|
+
* rate: string,
|
|
3892
|
+
* fromCurrencyExtraId: string|undefined
|
|
3893
|
+
* }|{
|
|
3894
|
+
* result: false,
|
|
3895
|
+
* reason: string,
|
|
3896
|
+
* partner: string
|
|
3897
|
+
* })>}
|
|
3898
|
+
*/
|
|
3899
|
+
async createSwap(fromCoin, toCoin, amount, toAddress, refundAddress, rawSwapData = null, clientIpAddress, toCurrencyExtraId = "", refundExtraId = "") {
|
|
3900
|
+
throw new Error("Not implemented in base");
|
|
3901
|
+
}
|
|
3902
|
+
|
|
3903
|
+
/**
|
|
3904
|
+
* Retrieves details and status for swaps by given ids.
|
|
3905
|
+
* If some swap is not found by id then there is no item in return list.
|
|
3906
|
+
*
|
|
3907
|
+
* @param swapIds {string[]}
|
|
3908
|
+
* @return {Promise<{result: false, reason: string}|{result:true, swaps: ExistingSwap[]}>}
|
|
3909
|
+
*/
|
|
3910
|
+
async getExistingSwapsDetailsAndStatus(swapIds) {
|
|
3911
|
+
throw new Error("Not implemented in base");
|
|
3912
|
+
}
|
|
3913
|
+
|
|
3914
|
+
/**
|
|
3915
|
+
* @param ticker {string}
|
|
3916
|
+
* @return {Coin|null}
|
|
3917
|
+
*/
|
|
3918
|
+
getCoinByTickerIfPresent(ticker) {
|
|
3919
|
+
throw new Error("Not implemented in base");
|
|
3920
|
+
}
|
|
3921
|
+
|
|
3922
|
+
/**
|
|
3923
|
+
* @param asset {Coin}
|
|
3924
|
+
* @param address {string}
|
|
3925
|
+
* @return {boolean}
|
|
3926
|
+
*/
|
|
3927
|
+
isAddressValidForAsset(asset, address) {
|
|
3928
|
+
throw new Error("Not implemented in base");
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3931
|
+
/**
|
|
3932
|
+
* @param asset {Coin}
|
|
3933
|
+
* @return {string|null}
|
|
3934
|
+
*/
|
|
3935
|
+
getExtraIdNameIfPresent(asset) {
|
|
3936
|
+
throw new Error("Not implemented in base");
|
|
3937
|
+
}
|
|
3938
|
+
}
|
|
3939
|
+
SwapProvider.COMMON_ERRORS = {
|
|
3940
|
+
REQUESTS_LIMIT_EXCEEDED: "requestsLimitExceeded"
|
|
3941
|
+
};
|
|
3942
|
+
SwapProvider.NO_SWAPS_REASONS = {
|
|
3943
|
+
TOO_LOW: "tooLow",
|
|
3944
|
+
TOO_HIGH: "tooHigh",
|
|
3945
|
+
NOT_SUPPORTED: "notSupported"
|
|
3946
|
+
};
|
|
3947
|
+
SwapProvider.CREATION_FAIL_REASONS = {
|
|
3948
|
+
RETRIABLE_FAIL: "retriableFail"
|
|
3949
|
+
};
|
|
3950
|
+
SwapProvider.SWAP_STATUSES = {
|
|
3951
|
+
WAITING_FOR_PAYMENT: "waiting_for_payment",
|
|
3952
|
+
// public +
|
|
3953
|
+
CONFIRMING: "confirming",
|
|
3954
|
+
PAYMENT_RECEIVED: "payment_received",
|
|
3955
|
+
// public +
|
|
3956
|
+
EXCHANGING: "exchanging",
|
|
3957
|
+
// session full // public +
|
|
3958
|
+
COMPLETED: "completed",
|
|
3959
|
+
// session full // public +
|
|
3960
|
+
REFUNDED: "refunded",
|
|
3961
|
+
// session full // public +
|
|
3962
|
+
EXPIRED: "expired",
|
|
3963
|
+
// public +
|
|
3964
|
+
FAILED: "failed" // public +
|
|
3965
|
+
};
|
|
3966
|
+
|
|
3967
|
+
const BANNED_PARTNERS = ["stealthex", "changee", "coincraddle"];
|
|
3968
|
+
const FALLBACK_ICON_URL = "https://rabbit.io/asset-icons/fallback.svg";
|
|
3969
|
+
class SwapspaceSwapProvider extends SwapProvider {
|
|
3970
|
+
constructor(apiKeysProxyUrl, cache, customCoinBuilder = (coin, network) => null, useRestrictedCoinsSet = true) {
|
|
3971
|
+
super();
|
|
3972
|
+
this._supportedCoins = [];
|
|
3973
|
+
this._URL = `${apiKeysProxyUrl}`;
|
|
3974
|
+
this._maxRateDigits = 20;
|
|
3975
|
+
this.useRestrictedCoinsSet = useRestrictedCoinsSet;
|
|
3976
|
+
this._customCoinBuilder = customCoinBuilder;
|
|
3977
|
+
this._cache = cache;
|
|
3978
|
+
}
|
|
3979
|
+
getSwapCreationInfoTtlMs() {
|
|
3980
|
+
/* Actually 2 minutes and only relevant for some partners, but we use it
|
|
3981
|
+
* (and even a bit smaller value) for better consistency */
|
|
3982
|
+
return 110000;
|
|
3983
|
+
}
|
|
3984
|
+
async getDepositCurrencies() {
|
|
3985
|
+
const loggerSource = "getDepositCurrencies";
|
|
3986
|
+
try {
|
|
3987
|
+
var _this$_supportedCoins;
|
|
3988
|
+
await this._fetchSupportedCurrenciesIfNeeded();
|
|
3989
|
+
Logger.log(`We have ${(_this$_supportedCoins = this._supportedCoins) == null ? void 0 : _this$_supportedCoins.length} supported coins, getting depositable`, loggerSource);
|
|
3990
|
+
return {
|
|
3991
|
+
result: true,
|
|
3992
|
+
coins: this._supportedCoins.filter(item => item.deposit).map(item => item.coin)
|
|
3993
|
+
};
|
|
3994
|
+
} catch (e) {
|
|
3995
|
+
var _e$response;
|
|
3996
|
+
if ((e == null || (_e$response = e.response) == null ? void 0 : _e$response.status) === 429) {
|
|
3997
|
+
return {
|
|
3998
|
+
result: false,
|
|
3999
|
+
reason: SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED
|
|
4000
|
+
};
|
|
4001
|
+
}
|
|
4002
|
+
improveAndRethrow(e, loggerSource);
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
async getAllSupportedCurrencies() {
|
|
4006
|
+
const loggerSource = "getAllSupportedCurrencies";
|
|
4007
|
+
try {
|
|
4008
|
+
var _this$_supportedCoins2;
|
|
4009
|
+
await this._fetchSupportedCurrenciesIfNeeded();
|
|
4010
|
+
Logger.log(`We have ${(_this$_supportedCoins2 = this._supportedCoins) == null ? void 0 : _this$_supportedCoins2.length} supported coins returning`, loggerSource);
|
|
4011
|
+
return {
|
|
4012
|
+
result: true,
|
|
4013
|
+
coins: this._supportedCoins.map(item => item.coin)
|
|
4014
|
+
};
|
|
4015
|
+
} catch (e) {
|
|
4016
|
+
var _e$response2;
|
|
4017
|
+
if ((e == null || (_e$response2 = e.response) == null ? void 0 : _e$response2.status) === 429) {
|
|
4018
|
+
return {
|
|
4019
|
+
result: false,
|
|
4020
|
+
reason: SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED
|
|
4021
|
+
};
|
|
4022
|
+
}
|
|
4023
|
+
improveAndRethrow(e, loggerSource);
|
|
4024
|
+
}
|
|
4025
|
+
}
|
|
4026
|
+
async getWithdrawalCurrencies(exceptCurrency = null) {
|
|
4027
|
+
const loggerSource = "getWithdrawalCurrencies";
|
|
4028
|
+
try {
|
|
4029
|
+
var _this$_supportedCoins3;
|
|
4030
|
+
await this._fetchSupportedCurrenciesIfNeeded();
|
|
4031
|
+
Logger.log(`We have ${(_this$_supportedCoins3 = this._supportedCoins) == null ? void 0 : _this$_supportedCoins3.length} supported coins, getting withdrawable`, loggerSource);
|
|
4032
|
+
return {
|
|
4033
|
+
result: true,
|
|
4034
|
+
coins: this._supportedCoins.filter(item => {
|
|
4035
|
+
var _item$coin;
|
|
4036
|
+
return item.withdrawal && (!exceptCurrency || ((_item$coin = item.coin) == null ? void 0 : _item$coin.ticker) !== (exceptCurrency == null ? void 0 : exceptCurrency.ticker));
|
|
4037
|
+
}).map(item => item.coin)
|
|
4038
|
+
};
|
|
4039
|
+
} catch (e) {
|
|
4040
|
+
var _e$response3;
|
|
4041
|
+
if ((e == null || (_e$response3 = e.response) == null ? void 0 : _e$response3.status) === 429) {
|
|
4042
|
+
return {
|
|
4043
|
+
result: false,
|
|
4044
|
+
reason: SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED
|
|
4045
|
+
};
|
|
4046
|
+
}
|
|
4047
|
+
improveAndRethrow(e, loggerSource);
|
|
4048
|
+
}
|
|
4049
|
+
}
|
|
4050
|
+
async initialize() {
|
|
4051
|
+
await this._fetchSupportedCurrenciesIfNeeded();
|
|
4052
|
+
}
|
|
4053
|
+
getIconUrl(coinOrTicker) {
|
|
4054
|
+
const loggerSource = "getIconUrl";
|
|
4055
|
+
try {
|
|
4056
|
+
var _this$_supportedCoins5, _this$_supportedCoins6;
|
|
4057
|
+
let coin = coinOrTicker;
|
|
4058
|
+
if (!(coinOrTicker instanceof Coin)) {
|
|
4059
|
+
var _this$_supportedCoins4;
|
|
4060
|
+
coin = (_this$_supportedCoins4 = this._supportedCoins.find(i => i.coin.ticker === coinOrTicker)) == null ? void 0 : _this$_supportedCoins4.coin;
|
|
4061
|
+
}
|
|
4062
|
+
return (_this$_supportedCoins5 = (_this$_supportedCoins6 = this._supportedCoins.find(item => {
|
|
4063
|
+
var _item$coin2, _coin;
|
|
4064
|
+
return ((_item$coin2 = item.coin) == null ? void 0 : _item$coin2.ticker) === ((_coin = coin) == null ? void 0 : _coin.ticker);
|
|
4065
|
+
})) == null ? void 0 : _this$_supportedCoins6.iconURL) != null ? _this$_supportedCoins5 : FALLBACK_ICON_URL;
|
|
4066
|
+
} catch (e) {
|
|
4067
|
+
improveAndRethrow(e, loggerSource);
|
|
4068
|
+
}
|
|
4069
|
+
}
|
|
4070
|
+
async _fetchSupportedCurrenciesIfNeeded() {
|
|
4071
|
+
const loggerSource = "_fetchSupportedCurrenciesIfNeeded";
|
|
4072
|
+
try {
|
|
4073
|
+
var _this$_supportedCoins7;
|
|
4074
|
+
if (!((_this$_supportedCoins7 = this._supportedCoins) != null && _this$_supportedCoins7.length)) {
|
|
4075
|
+
var _rawResponse$data, _rawResponse$data2;
|
|
4076
|
+
const rawResponse = await axios.get(`${this._URL}/api/v2/currencies`);
|
|
4077
|
+
Logger.log(`Retrieved ${rawResponse == null || (_rawResponse$data = rawResponse.data) == null ? void 0 : _rawResponse$data.length}`, loggerSource);
|
|
4078
|
+
let allowedCoins = (_rawResponse$data2 = rawResponse == null ? void 0 : rawResponse.data) != null ? _rawResponse$data2 : [];
|
|
4079
|
+
Logger.log(`Allowed cnt ${allowedCoins.length}`, loggerSource);
|
|
4080
|
+
this._supportedCoins = allowedCoins.map(item => {
|
|
4081
|
+
let coin = this._customCoinBuilder(item.code, item.network);
|
|
4082
|
+
if (!coin && !this.useRestrictedCoinsSet) {
|
|
4083
|
+
/** Building coin object for coin that isn't supported OOB in Rabbit.
|
|
4084
|
+
* We are doing this way to be able to use extended coins set for swaps.
|
|
4085
|
+
* These temporary built coins are only for in-swap use, and we omit some usual
|
|
4086
|
+
* coin details here.
|
|
4087
|
+
* Ideally we should add some new abstractions e.g. BaseCoin:
|
|
4088
|
+
* Coin will extend BaseCoin, SwapCoin will extend BaseCoin etc.
|
|
4089
|
+
* But for now it is reasonable to use this simpler approach.
|
|
4090
|
+
*/
|
|
4091
|
+
const code = item.code.toUpperCase();
|
|
4092
|
+
const network = item.network.toUpperCase();
|
|
4093
|
+
const ticker = `${code}${code === network ? "" : network}`;
|
|
4094
|
+
const defaultDecimalPlacesForCoinNotSupportedOOB = 8;
|
|
4095
|
+
const defaultMinConfirmationsForCoinNotSupportedOOB = 1;
|
|
4096
|
+
// TODO: [dev] maybe we should recognize standard protocols?
|
|
4097
|
+
coin = new Coin(item.name, ticker, code, defaultDecimalPlacesForCoinNotSupportedOOB, null, "", null, null, defaultMinConfirmationsForCoinNotSupportedOOB, null, [], 60000, null,
|
|
4098
|
+
// We cannot recognize blockchain from swapspace data
|
|
4099
|
+
code !== network ? new Protocol(network) : null, item.contractAddress || null, false);
|
|
4100
|
+
}
|
|
4101
|
+
if (coin) {
|
|
4102
|
+
var _item$deposit, _item$withdrawal, _item$validationRegex;
|
|
4103
|
+
return {
|
|
4104
|
+
coin: coin,
|
|
4105
|
+
code: item.code,
|
|
4106
|
+
network: item.network,
|
|
4107
|
+
hasExtraId: item.hasExtraId,
|
|
4108
|
+
extraIdName: item.extraIdName,
|
|
4109
|
+
isPopular: !!(item != null && item.popular),
|
|
4110
|
+
iconURL: item.icon ? `https://storage.swapspace.co${item.icon}` : FALLBACK_ICON_URL,
|
|
4111
|
+
deposit: (_item$deposit = item.deposit) != null ? _item$deposit : false,
|
|
4112
|
+
withdrawal: (_item$withdrawal = item.withdrawal) != null ? _item$withdrawal : false,
|
|
4113
|
+
validationRegexp: (_item$validationRegex = item.validationRegexp) != null ? _item$validationRegex : null
|
|
4114
|
+
};
|
|
4115
|
+
}
|
|
4116
|
+
return [];
|
|
4117
|
+
}).flat();
|
|
4118
|
+
this._putPopularCoinsFirst();
|
|
4119
|
+
}
|
|
4120
|
+
} catch (e) {
|
|
4121
|
+
improveAndRethrow(e, loggerSource);
|
|
4122
|
+
}
|
|
4123
|
+
}
|
|
4124
|
+
|
|
4125
|
+
/**
|
|
4126
|
+
* This method sort internal list putting popular (as swapspace thinks) coins to the top.
|
|
4127
|
+
* This is just for users of this API if they don't care about the sorting - we just improve a list a bit this way.
|
|
4128
|
+
* @private
|
|
4129
|
+
*/
|
|
4130
|
+
_putPopularCoinsFirst() {
|
|
4131
|
+
this._supportedCoins.sort((i1, i2) => {
|
|
4132
|
+
if (i1.isPopular && !i2.isPopular) return -1;
|
|
4133
|
+
if (i2.isPopular && !i1.isPopular) return 1;
|
|
4134
|
+
return i1.coin.ticker > i2.coin.ticker ? 1 : i1.coin.ticker < i2.coin.ticker ? -1 : 0;
|
|
4135
|
+
});
|
|
4136
|
+
}
|
|
4137
|
+
async getCoinToUSDTRate(coin) {
|
|
4138
|
+
const loggerSource = "getCoinToUSDTRate";
|
|
4139
|
+
try {
|
|
4140
|
+
var _this$_supportedCoins8;
|
|
4141
|
+
if (!coin) return null;
|
|
4142
|
+
await this._fetchSupportedCurrenciesIfNeeded();
|
|
4143
|
+
|
|
4144
|
+
// Using USDT TRC20 as usually fee in this network is smaller than ERC20 and this network is widely used for USDT
|
|
4145
|
+
const usdtTrc20 = (_this$_supportedCoins8 = this._supportedCoins.find(i => i.coin.ticker === "USDTTRC20")) == null ? void 0 : _this$_supportedCoins8.coin;
|
|
4146
|
+
if (!usdtTrc20) {
|
|
4147
|
+
return {
|
|
4148
|
+
result: false
|
|
4149
|
+
};
|
|
4150
|
+
}
|
|
4151
|
+
const cached = this._cache.get("swapspace_usdt_rate_" + coin.ticker);
|
|
4152
|
+
if (cached != null) {
|
|
4153
|
+
return {
|
|
4154
|
+
result: true,
|
|
4155
|
+
rate: cached
|
|
4156
|
+
};
|
|
4157
|
+
}
|
|
4158
|
+
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");
|
|
4160
|
+
if (!result.result) {
|
|
4161
|
+
return {
|
|
4162
|
+
result: false
|
|
4163
|
+
};
|
|
4164
|
+
}
|
|
4165
|
+
|
|
4166
|
+
// This calculation is not precise as we cannot recognize the actual fee and network fee. Just approximate.
|
|
4167
|
+
const standardSwapspaceFeeMultiplier = 1.004; // usually 0.2%
|
|
4168
|
+
const rate = BigNumber(1).div(BigNumber(result.rate).times(standardSwapspaceFeeMultiplier)).toString();
|
|
4169
|
+
this._cache.put("swapspace_usdt_rate_" + coin.ticker, rate, 15 * 60000 // 15 minutes
|
|
4170
|
+
);
|
|
4171
|
+
return {
|
|
4172
|
+
result: true,
|
|
4173
|
+
rate: rate
|
|
4174
|
+
};
|
|
4175
|
+
} catch (e) {
|
|
4176
|
+
improveAndRethrow(e, loggerSource);
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
4179
|
+
getCoinByTickerIfPresent(ticker) {
|
|
4180
|
+
try {
|
|
4181
|
+
var _item$coin3;
|
|
4182
|
+
const item = this._supportedCoins.find(i => i.coin.ticker === ticker);
|
|
4183
|
+
return (_item$coin3 = item == null ? void 0 : item.coin) != null ? _item$coin3 : null;
|
|
4184
|
+
} catch (e) {
|
|
4185
|
+
improveAndRethrow(e, "getCoinByTickerIfPresent");
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4188
|
+
async getSwapInfo(fromCoin, toCoin, amountCoins, fromCoinToUsdRate = null) {
|
|
4189
|
+
const loggerSource = "getSwapInfo";
|
|
4190
|
+
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}` + (fromCoin instanceof Coin) + (toCoin instanceof Coin));
|
|
4194
|
+
}
|
|
4195
|
+
const fromCoinSwapspaceDetails = this._supportedCoins.find(i => {
|
|
4196
|
+
var _i$coin;
|
|
4197
|
+
return ((_i$coin = i.coin) == null ? void 0 : _i$coin.ticker) === (fromCoin == null ? void 0 : fromCoin.ticker);
|
|
4198
|
+
});
|
|
4199
|
+
const toCoinSwapspaceDetails = this._supportedCoins.find(i => {
|
|
4200
|
+
var _i$coin2;
|
|
4201
|
+
return ((_i$coin2 = i.coin) == null ? void 0 : _i$coin2.ticker) === (toCoin == null ? void 0 : toCoin.ticker);
|
|
4202
|
+
});
|
|
4203
|
+
if (!fromCoinSwapspaceDetails || !toCoinSwapspaceDetails) {
|
|
4204
|
+
throw new Error("Failed to find swapspace coin details for: " + fromCoin.ticker + " -> " + toCoin.ticker);
|
|
4205
|
+
}
|
|
4206
|
+
if (!fromCoinSwapspaceDetails.deposit || !toCoinSwapspaceDetails.withdrawal) {
|
|
4207
|
+
return {
|
|
4208
|
+
result: false,
|
|
4209
|
+
reason: SwapProvider.NO_SWAPS_REASONS.NOT_SUPPORTED
|
|
4210
|
+
};
|
|
4211
|
+
}
|
|
4212
|
+
/* Here we use not documented parameter 'estimated=false'. This parameter controls whether we want to use
|
|
4213
|
+
* cached rate values stored in swapspace cache. Their support says they store at most for 30 sec.
|
|
4214
|
+
* But we are better off using the most actual rates.
|
|
4215
|
+
*/
|
|
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}&float=true&estimated=false`);
|
|
4217
|
+
Logger.log(`Retrieved ${response == null || (_response$data = response.data) == null ? void 0 : _response$data.length} options`, loggerSource);
|
|
4218
|
+
const options = Array.isArray(response.data) ? response.data : [];
|
|
4219
|
+
const exchangesSupportingThePair = 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.min === 0 || exchange.max === 0 || exchange.max > exchange.min || (typeof exchange.min !== "number" || typeof exchange.max !== "number") && exchange.toAmount > 0));
|
|
4220
|
+
Logger.log(`${exchangesSupportingThePair == null ? void 0 : exchangesSupportingThePair.length} of them have exist=true`, loggerSource);
|
|
4221
|
+
if (!exchangesSupportingThePair.length) {
|
|
4222
|
+
return {
|
|
4223
|
+
result: false,
|
|
4224
|
+
reason: SwapProvider.NO_SWAPS_REASONS.NOT_SUPPORTED
|
|
4225
|
+
};
|
|
4226
|
+
}
|
|
4227
|
+
const availableExchanges = exchangesSupportingThePair.filter(exchange => typeof (exchange == null ? void 0 : exchange.toAmount) === "number" && exchange.toAmount > 0);
|
|
4228
|
+
Logger.log(`Available (having amountTo): ${safeStringify(availableExchanges)}`, loggerSource);
|
|
4229
|
+
// min=0 or max=0 means there is no limit for the partner
|
|
4230
|
+
let smallestMin = null;
|
|
4231
|
+
if (exchangesSupportingThePair.find(ex => BigNumber(ex.min).isZero()) == null) {
|
|
4232
|
+
smallestMin = exchangesSupportingThePair.reduce((prev, cur) => {
|
|
4233
|
+
if (typeof cur.min === "number" && (prev === null || BigNumber(cur.min).lt(prev))) return BigNumber(cur.min);
|
|
4234
|
+
return prev;
|
|
4235
|
+
}, null);
|
|
4236
|
+
}
|
|
4237
|
+
let greatestMax = null;
|
|
4238
|
+
if (exchangesSupportingThePair.find(ex => BigNumber(ex.max).isZero()) == null) {
|
|
4239
|
+
greatestMax = exchangesSupportingThePair.reduce((prev, cur) => {
|
|
4240
|
+
if (typeof cur.max === "number" && (prev === null || BigNumber(cur.max).gt(prev))) return BigNumber(cur.max);
|
|
4241
|
+
return prev;
|
|
4242
|
+
}, null);
|
|
4243
|
+
}
|
|
4244
|
+
let extraCoinsToFitMinMax = "0";
|
|
4245
|
+
if (typeof fromCoinToUsdRate === "string" && BigNumber(fromCoinToUsdRate).gt("0")) {
|
|
4246
|
+
const extraUsdToFitMinMax = BigNumber("1"); // We correct the limits as the exact limit can fluctuate and cause failed swap creation
|
|
4247
|
+
extraCoinsToFitMinMax = AmountUtils.trim(extraUsdToFitMinMax.div(fromCoinToUsdRate), fromCoin.digits);
|
|
4248
|
+
}
|
|
4249
|
+
if (smallestMin instanceof BigNumber) {
|
|
4250
|
+
smallestMin = AmountUtils.trim(smallestMin.plus(extraCoinsToFitMinMax), fromCoin.digits);
|
|
4251
|
+
}
|
|
4252
|
+
if (greatestMax instanceof BigNumber) {
|
|
4253
|
+
if (greatestMax > extraCoinsToFitMinMax) {
|
|
4254
|
+
greatestMax = AmountUtils.trim(greatestMax.minus(extraCoinsToFitMinMax), fromCoin.digits);
|
|
4255
|
+
} else {
|
|
4256
|
+
greatestMax = "0";
|
|
4257
|
+
}
|
|
4258
|
+
}
|
|
4259
|
+
if (availableExchanges.length) {
|
|
4260
|
+
var _bestOpt$duration;
|
|
4261
|
+
const sorted = availableExchanges.sort((op1, op2) => op2.toAmount - op1.toAmount);
|
|
4262
|
+
const bestOpt = sorted[0];
|
|
4263
|
+
Logger.log(`Returning first option after sorting: ${safeStringify(bestOpt)}`, loggerSource);
|
|
4264
|
+
let max = null;
|
|
4265
|
+
let min = null;
|
|
4266
|
+
if (extraCoinsToFitMinMax != null) {
|
|
4267
|
+
if (typeof bestOpt.max === "number" && bestOpt.max !== 0) {
|
|
4268
|
+
max = BigNumber(bestOpt.max).minus(extraCoinsToFitMinMax);
|
|
4269
|
+
max = AmountUtils.trim(max.lt(0) ? "0" : max, fromCoin.digits);
|
|
4270
|
+
}
|
|
4271
|
+
if (typeof bestOpt.min === "number" && bestOpt.min !== 0) {
|
|
4272
|
+
min = AmountUtils.trim(BigNumber(bestOpt.min).plus(extraCoinsToFitMinMax), fromCoin.digits);
|
|
4273
|
+
}
|
|
4274
|
+
}
|
|
4275
|
+
const rate = bestOpt.toAmount && bestOpt.fromAmount ? BigNumber(bestOpt.toAmount).div(bestOpt.fromAmount) : null;
|
|
4276
|
+
return {
|
|
4277
|
+
result: true,
|
|
4278
|
+
min: min,
|
|
4279
|
+
max: max,
|
|
4280
|
+
smallestMin: smallestMin,
|
|
4281
|
+
greatestMax: greatestMax,
|
|
4282
|
+
rate: rate != null ? AmountUtils.trim(rate, this._maxRateDigits) : null,
|
|
4283
|
+
durationMinutesRange: (_bestOpt$duration = bestOpt.duration) != null ? _bestOpt$duration : null,
|
|
4284
|
+
rawSwapData: bestOpt
|
|
4285
|
+
};
|
|
4286
|
+
}
|
|
4287
|
+
const result = {
|
|
4288
|
+
result: false,
|
|
4289
|
+
reason: smallestMin && BigNumber(amountCoins).lt(smallestMin) ? SwapProvider.NO_SWAPS_REASONS.TOO_LOW : greatestMax && BigNumber(amountCoins).gt(greatestMax) ? SwapProvider.NO_SWAPS_REASONS.TOO_HIGH : SwapProvider.NO_SWAPS_REASONS.NOT_SUPPORTED,
|
|
4290
|
+
smallestMin: smallestMin,
|
|
4291
|
+
greatestMax: greatestMax
|
|
4292
|
+
};
|
|
4293
|
+
Logger.log(`Returning result ${safeStringify(result)}`, loggerSource);
|
|
4294
|
+
return result;
|
|
4295
|
+
} catch (e) {
|
|
4296
|
+
var _e$response4;
|
|
4297
|
+
if ((e == null || (_e$response4 = e.response) == null ? void 0 : _e$response4.status) === 429) {
|
|
4298
|
+
return {
|
|
4299
|
+
result: false,
|
|
4300
|
+
reason: SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED
|
|
4301
|
+
};
|
|
4302
|
+
}
|
|
4303
|
+
Logger.log(`Internal swapspace/rabbit error when getting swap options ${safeStringify(e)}`, loggerSource);
|
|
4304
|
+
improveAndRethrow(e, loggerSource);
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
async createSwap(fromCoin, toCoin, amount, toAddress, refundAddress, rawSwapData, clientIpAddress, toCurrencyExtraId = "", refundExtraId = "") {
|
|
4308
|
+
const loggerSource = "createSwap";
|
|
4309
|
+
const partner = rawSwapData == null ? void 0 : rawSwapData.partner;
|
|
4310
|
+
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}`);
|
|
4313
|
+
}
|
|
4314
|
+
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
|
+
) {
|
|
4316
|
+
throw new Error(`Invalid raw swap data: ${safeStringify(rawSwapData)}`);
|
|
4317
|
+
}
|
|
4318
|
+
await this._fetchSupportedCurrenciesIfNeeded();
|
|
4319
|
+
const requestData = {
|
|
4320
|
+
partner: partner,
|
|
4321
|
+
fromCurrency: rawSwapData == null ? void 0 : rawSwapData.fromCurrency,
|
|
4322
|
+
fromNetwork: rawSwapData == null ? void 0 : rawSwapData.fromNetwork,
|
|
4323
|
+
toCurrency: rawSwapData == null ? void 0 : rawSwapData.toCurrency,
|
|
4324
|
+
toNetwork: rawSwapData == null ? void 0 : rawSwapData.toNetwork,
|
|
4325
|
+
address: toAddress,
|
|
4326
|
+
amount: amount,
|
|
4327
|
+
fixed: false,
|
|
4328
|
+
extraId: toCurrencyExtraId != null ? toCurrencyExtraId : "",
|
|
4329
|
+
refundExtraId: refundExtraId != null ? refundExtraId : "",
|
|
4330
|
+
// This param is not documented. But the refund is usually manual so this is not critical.
|
|
4331
|
+
rateId: rawSwapData == null ? void 0 : rawSwapData.id,
|
|
4332
|
+
userIp: clientIpAddress,
|
|
4333
|
+
refund: refundAddress
|
|
4334
|
+
};
|
|
4335
|
+
Logger.log(`Sending create request: ${safeStringify(requestData)}`, loggerSource);
|
|
4336
|
+
const response = await axios.post(`${this._URL}/api/v2/exchange`, requestData);
|
|
4337
|
+
const result = response.data;
|
|
4338
|
+
Logger.log(`Creation result ${safeStringify(result)}`, loggerSource);
|
|
4339
|
+
if (result != null && result.id) {
|
|
4340
|
+
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}`);
|
|
4342
|
+
/* We use the returned rate preferably but if the retrieved
|
|
4343
|
+
* rate 0/null/undefined we calculate it manually */
|
|
4344
|
+
let rate = result.rate;
|
|
4345
|
+
if (typeof rate !== "number" || BigNumber(rate).isZero()) {
|
|
4346
|
+
var _result$to3, _result$from3;
|
|
4347
|
+
rate = BigNumber(result == null || (_result$to3 = result.to) == null ? void 0 : _result$to3.amount).div(result == null || (_result$from3 = result.from) == null ? void 0 : _result$from3.amount);
|
|
4348
|
+
} else {
|
|
4349
|
+
rate = BigNumber(rate);
|
|
4350
|
+
}
|
|
4351
|
+
return {
|
|
4352
|
+
result: true,
|
|
4353
|
+
swapId: result == null ? void 0 : result.id,
|
|
4354
|
+
fromCoin: fromCoin,
|
|
4355
|
+
fromAmount: AmountUtils.trim(result == null || (_result$from4 = result.from) == null ? void 0 : _result$from4.amount, fromCoin.digits),
|
|
4356
|
+
fromAddress: result == null || (_result$from5 = result.from) == null ? void 0 : _result$from5.address,
|
|
4357
|
+
toCoin: toCoin,
|
|
4358
|
+
toAmount: AmountUtils.trim(result == null || (_result$to4 = result.to) == null ? void 0 : _result$to4.amount, toCoin.digits),
|
|
4359
|
+
toAddress: result == null || (_result$to5 = result.to) == null ? void 0 : _result$to5.address,
|
|
4360
|
+
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)
|
|
4362
|
+
};
|
|
4363
|
+
}
|
|
4364
|
+
const errorMessage = `Swap creation succeeded but the response is wrong: ${safeStringify(response)}`;
|
|
4365
|
+
Logger.log(errorMessage, loggerSource);
|
|
4366
|
+
throw new Error(errorMessage);
|
|
4367
|
+
} catch (e) {
|
|
4368
|
+
var _e$response5, _e$response6;
|
|
4369
|
+
Logger.logError(e, loggerSource, `Failed to create swap. Error is: ${safeStringify(e)}`);
|
|
4370
|
+
const composeFailResult = reason => ({
|
|
4371
|
+
result: false,
|
|
4372
|
+
reason: reason,
|
|
4373
|
+
partner: partner
|
|
4374
|
+
});
|
|
4375
|
+
const status = e == null || (_e$response5 = e.response) == null ? void 0 : _e$response5.status;
|
|
4376
|
+
const data = e == null || (_e$response6 = e.response) == null ? void 0 : _e$response6.data;
|
|
4377
|
+
if (status === 429) {
|
|
4378
|
+
Logger.log(`Returning fail - RPS limit exceeded ${data}`, loggerSource);
|
|
4379
|
+
return composeFailResult(SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED);
|
|
4380
|
+
}
|
|
4381
|
+
const texts422 = ["Pair cannot be processed by", "Currency not found", "Amount maximum is", "Amount minimum is"];
|
|
4382
|
+
const text403 = "IP address is forbidden";
|
|
4383
|
+
if (typeof data === "string" && (status === 403 && data.includes(text403) || status === 422 && texts422.find(text => data.includes(text)))) {
|
|
4384
|
+
Logger.log(`Returning retriable fail: ${status} - ${data}, ${partner}`, loggerSource);
|
|
4385
|
+
return composeFailResult(SwapProvider.CREATION_FAIL_REASONS.RETRIABLE_FAIL);
|
|
4386
|
+
}
|
|
4387
|
+
Logger.log(`Internal swapspace/rabbit error for ${partner}: ${safeStringify(e)}`, loggerSource);
|
|
4388
|
+
improveAndRethrow(e, loggerSource);
|
|
4389
|
+
}
|
|
4390
|
+
}
|
|
4391
|
+
_mapSwapspaceStatusToRabbitStatus(status, isExpiredByTime) {
|
|
4392
|
+
switch (status) {
|
|
4393
|
+
case "waiting":
|
|
4394
|
+
if (isExpiredByTime) {
|
|
4395
|
+
return SwapProvider.SWAP_STATUSES.EXPIRED;
|
|
4396
|
+
}
|
|
4397
|
+
return SwapProvider.SWAP_STATUSES.WAITING_FOR_PAYMENT;
|
|
4398
|
+
case "confirming":
|
|
4399
|
+
return SwapProvider.SWAP_STATUSES.CONFIRMING;
|
|
4400
|
+
case "exchanging":
|
|
4401
|
+
return SwapProvider.SWAP_STATUSES.EXCHANGING;
|
|
4402
|
+
case "sending":
|
|
4403
|
+
return SwapProvider.SWAP_STATUSES.PAYMENT_RECEIVED;
|
|
4404
|
+
case "finished":
|
|
4405
|
+
return SwapProvider.SWAP_STATUSES.COMPLETED;
|
|
4406
|
+
case "verifying":
|
|
4407
|
+
return SwapProvider.SWAP_STATUSES.EXCHANGING;
|
|
4408
|
+
case "refunded":
|
|
4409
|
+
return SwapProvider.SWAP_STATUSES.REFUNDED;
|
|
4410
|
+
case "expired":
|
|
4411
|
+
return SwapProvider.SWAP_STATUSES.EXPIRED;
|
|
4412
|
+
case "failed":
|
|
4413
|
+
return SwapProvider.SWAP_STATUSES.FAILED;
|
|
4414
|
+
default:
|
|
4415
|
+
throw new Error(`Unknown swapspace status: ${status}`);
|
|
4416
|
+
}
|
|
4417
|
+
}
|
|
4418
|
+
async getExistingSwapsDetailsAndStatus(swapIds) {
|
|
4419
|
+
var _this = this;
|
|
4420
|
+
const loggerSource = "getExistingSwapsDetailsAndStatus";
|
|
4421
|
+
try {
|
|
4422
|
+
if (swapIds.find(id => typeof id !== "string")) {
|
|
4423
|
+
throw new Error("Swap id is not string: " + safeStringify(swapIds));
|
|
4424
|
+
}
|
|
4425
|
+
const getNotFailingOn404 = async function getNotFailingOn404(swapId) {
|
|
4426
|
+
try {
|
|
4427
|
+
return await axios.get(`${_this._URL}/api/v2/exchange/${swapId}`);
|
|
4428
|
+
} catch (error) {
|
|
4429
|
+
var _error$response;
|
|
4430
|
+
if ((error == null || (_error$response = error.response) == null ? void 0 : _error$response.status) === 404) return [];
|
|
4431
|
+
throw error;
|
|
4432
|
+
}
|
|
4433
|
+
};
|
|
4434
|
+
const responses = await Promise.all(swapIds.map(swapId => getNotFailingOn404(swapId)));
|
|
4435
|
+
const wo404 = responses.flat();
|
|
4436
|
+
Logger.log("All swaps RAW: " + JSON.stringify(wo404.map(r => r.data)), loggerSource);
|
|
4437
|
+
const swaps = wo404.map(r => r.data).map((swap, index) => {
|
|
4438
|
+
var _this$_supportedCoins9, _this$_supportedCoins10, _swap$from$extraId, _swap$to$extraId, _swap$refundExtraId;
|
|
4439
|
+
const fromCoin = (_this$_supportedCoins9 = this._supportedCoins.find(i => i.code === swap.from.code && i.network === swap.from.network)) == null ? void 0 : _this$_supportedCoins9.coin;
|
|
4440
|
+
const toCoin = (_this$_supportedCoins10 = this._supportedCoins.find(i => i.code === swap.to.code && i.network === swap.to.network)) == null ? void 0 : _this$_supportedCoins10.coin;
|
|
4441
|
+
if (!fromCoin || !toCoin) {
|
|
4442
|
+
return []; // We skip swaps with not supported coins for now
|
|
4443
|
+
}
|
|
4444
|
+
const toUtcTimestamp = timeStr => Date.parse(timeStr.match(/.+[Zz]$/) ? timeStr : `${timeStr}Z`);
|
|
4445
|
+
const expiresAt = toUtcTimestamp(swap.timestamps.expiresAt);
|
|
4446
|
+
const isExpiredByTime = expiresAt < Date.now();
|
|
4447
|
+
const status = this._mapSwapspaceStatusToRabbitStatus(swap.status, isExpiredByTime);
|
|
4448
|
+
const toDigits = status === SwapProvider.SWAP_STATUSES.REFUNDED ? fromCoin.digits : toCoin.digits;
|
|
4449
|
+
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);
|
|
4451
|
+
}).flat();
|
|
4452
|
+
Logger.log(`Swap details result ${safeStringify(swaps)}`, loggerSource);
|
|
4453
|
+
return {
|
|
4454
|
+
result: true,
|
|
4455
|
+
swaps: swaps
|
|
4456
|
+
};
|
|
4457
|
+
} catch (e) {
|
|
4458
|
+
var _e$response7, _e$response8;
|
|
4459
|
+
Logger.logError(e, loggerSource, `Failed to get swap details. Error is: ${safeStringify(e)}`);
|
|
4460
|
+
const composeFailResult = reason => ({
|
|
4461
|
+
result: false,
|
|
4462
|
+
reason: reason
|
|
4463
|
+
});
|
|
4464
|
+
const status = e == null || (_e$response7 = e.response) == null ? void 0 : _e$response7.status;
|
|
4465
|
+
const data = e == null || (_e$response8 = e.response) == null ? void 0 : _e$response8.data;
|
|
4466
|
+
if (status === 429) {
|
|
4467
|
+
Logger.log(`Returning fail - RPS limit exceeded ${data}`, loggerSource);
|
|
4468
|
+
return composeFailResult(SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED);
|
|
4469
|
+
}
|
|
4470
|
+
improveAndRethrow(e, loggerSource);
|
|
4471
|
+
}
|
|
4472
|
+
}
|
|
4473
|
+
isAddressValidForAsset(asset, address) {
|
|
4474
|
+
try {
|
|
4475
|
+
const assetData = this._supportedCoins.find(i => {
|
|
4476
|
+
var _i$coin3;
|
|
4477
|
+
return ((_i$coin3 = i.coin) == null ? void 0 : _i$coin3.ticker) === (asset == null ? void 0 : asset.ticker);
|
|
4478
|
+
});
|
|
4479
|
+
if (assetData) {
|
|
4480
|
+
let corrected = assetData.validationRegexp.trim();
|
|
4481
|
+
corrected = corrected[0] === "/" ? corrected.slice(1) : corrected;
|
|
4482
|
+
corrected = corrected[corrected.length - 1] === "/" ? corrected.slice(0, corrected.length - 1) : corrected;
|
|
4483
|
+
return address.match(corrected) != null;
|
|
4484
|
+
}
|
|
4485
|
+
} catch (e) {
|
|
4486
|
+
Logger.logError(e, "isAddressValidForAsset");
|
|
4487
|
+
}
|
|
4488
|
+
return false;
|
|
4489
|
+
}
|
|
4490
|
+
getExtraIdNameIfPresent(asset) {
|
|
4491
|
+
try {
|
|
4492
|
+
const assetData = this._supportedCoins.find(i => {
|
|
4493
|
+
var _i$coin4;
|
|
4494
|
+
return ((_i$coin4 = i.coin) == null ? void 0 : _i$coin4.ticker) === (asset == null ? void 0 : asset.ticker);
|
|
4495
|
+
});
|
|
4496
|
+
if (assetData != null && assetData.hasExtraId) {
|
|
4497
|
+
if ((assetData == null ? void 0 : assetData.extraIdName) == null || (assetData == null ? void 0 : assetData.extraIdName) === "") {
|
|
4498
|
+
// We return some default name if the extraIdName is empty
|
|
4499
|
+
return "ID";
|
|
4500
|
+
}
|
|
4501
|
+
return assetData == null ? void 0 : assetData.extraIdName;
|
|
4502
|
+
}
|
|
4503
|
+
return null;
|
|
4504
|
+
} catch (e) {
|
|
4505
|
+
improveAndRethrow(e, "getExtraIdNameIfPresent");
|
|
4506
|
+
}
|
|
4507
|
+
}
|
|
4508
|
+
}
|
|
4509
|
+
|
|
4510
|
+
class SwapUtils {
|
|
4511
|
+
/**
|
|
4512
|
+
* Retrieves min and max limits for swapping giving currencies.
|
|
4513
|
+
* Returns also conversion rate if possible with predefined amount logic.
|
|
4514
|
+
* Rate is how many "to" coins does 1 "from" coin contain.
|
|
4515
|
+
*
|
|
4516
|
+
* In case of errors returns one of reasons
|
|
4517
|
+
* - SwapProvider.NO_SWAPS_REASONS.NOT_SUPPORTED
|
|
4518
|
+
* - one of SwapProvider.COMMON_ERRORS.*
|
|
4519
|
+
*
|
|
4520
|
+
* @param swapProvider {SwapProvider}
|
|
4521
|
+
* @param fromCoin {Coin} enabled coin (to swap amount from)
|
|
4522
|
+
* @param toCoin {Coin}
|
|
4523
|
+
* @param coinToCurrentFiatRate {string|null}
|
|
4524
|
+
* @param fiatCurrencyDecimals {number|null}
|
|
4525
|
+
* @return {Promise<{
|
|
4526
|
+
* result: true,
|
|
4527
|
+
* min: string,
|
|
4528
|
+
* fiatMin: (number|null),
|
|
4529
|
+
* max: string,
|
|
4530
|
+
* fiatMax: (number|null),
|
|
4531
|
+
* rate: (string|null),
|
|
4532
|
+
* }|{
|
|
4533
|
+
* result: false,
|
|
4534
|
+
* reason: string
|
|
4535
|
+
* }>}
|
|
4536
|
+
*/
|
|
4537
|
+
static async getInitialSwapData(swapProvider, fromCoin, toCoin, coinToCurrentFiatRate = null, fiatCurrencyDecimals = null) {
|
|
4538
|
+
const loggerSource = "getInitialSwapData";
|
|
4539
|
+
try {
|
|
4540
|
+
/* We use some amount here that should fit at least some of the limits of the swap providers.
|
|
4541
|
+
* So we are going to get some rate to be used as the default for the on-flight calculations before we get
|
|
4542
|
+
* the exact rate (that should be retrieved by getSwapCreationInfo method) for a specific amount.
|
|
4543
|
+
*/
|
|
4544
|
+
const defaultAmountUsd = BigNumber("300");
|
|
4545
|
+
const coinUsdRate = await swapProvider.getCoinToUSDTRate(fromCoin);
|
|
4546
|
+
const coinAmountForDefaultUsdAmount = AmountUtils.trim(coinUsdRate.result ? defaultAmountUsd.div(coinUsdRate == null ? void 0 : coinUsdRate.rate) : defaultAmountUsd, fromCoin.digits);
|
|
4547
|
+
Logger.log(`Init: ${coinAmountForDefaultUsdAmount} ${fromCoin.ticker}->${toCoin.ticker}`, loggerSource);
|
|
4548
|
+
const details = await swapProvider.getSwapInfo(fromCoin, toCoin, coinAmountForDefaultUsdAmount);
|
|
4549
|
+
if (!details) {
|
|
4550
|
+
throw new Error("The details are empty: " + safeStringify(details));
|
|
4551
|
+
}
|
|
4552
|
+
if (!details.result) {
|
|
4553
|
+
Logger.log(`Failed with reason: ${details.reason}. ${fromCoin.ticker}->${toCoin.ticker}`, loggerSource);
|
|
4554
|
+
if ((details == null ? void 0 : details.reason) === SwapProvider.NO_SWAPS_REASONS.NOT_SUPPORTED || (details == null ? void 0 : details.reason) === SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED) {
|
|
4555
|
+
return {
|
|
4556
|
+
result: false,
|
|
4557
|
+
reason: details.reason
|
|
4558
|
+
};
|
|
4559
|
+
} else {
|
|
4560
|
+
throw new Error("Unhandled error case: " + (details == null ? void 0 : details.reason));
|
|
4561
|
+
}
|
|
4562
|
+
}
|
|
4563
|
+
let fiatMin = null;
|
|
4564
|
+
let fiatMax = null;
|
|
4565
|
+
const fiatRate = coinToCurrentFiatRate != null ? coinToCurrentFiatRate : coinUsdRate == null ? void 0 : coinUsdRate.rate;
|
|
4566
|
+
const fiatDecimals = fiatCurrencyDecimals != null ? fiatCurrencyDecimals : FiatCurrenciesService.getCurrencyDecimalCountByCode("USD");
|
|
4567
|
+
if (fiatRate != null) {
|
|
4568
|
+
fiatMin = BigNumber(details == null ? void 0 : details.smallestMin).times(fiatRate).toFixed(fiatDecimals);
|
|
4569
|
+
fiatMax = BigNumber(details == null ? void 0 : details.greatestMax).times(fiatRate).toFixed(fiatDecimals);
|
|
4570
|
+
}
|
|
4571
|
+
const result = {
|
|
4572
|
+
result: true,
|
|
4573
|
+
min: details == null ? void 0 : details.smallestMin,
|
|
4574
|
+
fiatMin: fiatMin,
|
|
4575
|
+
max: details == null ? void 0 : details.greatestMax,
|
|
4576
|
+
fiatMax: fiatMax,
|
|
4577
|
+
rate: AmountUtils.trim(details.rate, 30)
|
|
4578
|
+
};
|
|
4579
|
+
Logger.log(`Returning: ${safeStringify(result)}`, loggerSource);
|
|
4580
|
+
return result;
|
|
4581
|
+
} catch (e) {
|
|
4582
|
+
Logger.logError(e, loggerSource, `Failed to init swap: ${safeStringify(e)}`);
|
|
4583
|
+
improveAndRethrow(e, loggerSource);
|
|
4584
|
+
}
|
|
4585
|
+
}
|
|
4586
|
+
static safeHandleRequestsLimitExceeding() {
|
|
4587
|
+
(async function () {
|
|
4588
|
+
try {
|
|
4589
|
+
await EmailsApi.sendEmail("AUTOMATIC EMAIL - SWAPSPACE REQUESTS LIMIT EXCEEDED", "Requests limit exceeded. Urgently ask swaps provider support for limit increasing");
|
|
4590
|
+
} catch (e) {
|
|
4591
|
+
Logger.log(`Failed to handle limit exceeding ${safeStringify(e)}`, "_safeHandleRequestsLimitExceeding");
|
|
4592
|
+
}
|
|
4593
|
+
})();
|
|
4594
|
+
}
|
|
4595
|
+
|
|
4596
|
+
/**
|
|
4597
|
+
* If some swap is not found by id then there is no item in return list.
|
|
4598
|
+
*
|
|
4599
|
+
* @param swapProvider {SwapProvider}
|
|
4600
|
+
* @param swapIds {string[]}
|
|
4601
|
+
* @return {Promise<{
|
|
4602
|
+
* result: true,
|
|
4603
|
+
* swaps: ExistingSwapWithFiatData[]
|
|
4604
|
+
* }|{
|
|
4605
|
+
* result: false,
|
|
4606
|
+
* reason: string
|
|
4607
|
+
* }>}
|
|
4608
|
+
*/
|
|
4609
|
+
static async getExistingSwapsDetailsWithFiatAmounts(swapProvider, swapIds) {
|
|
4610
|
+
try {
|
|
4611
|
+
const result = await swapProvider.getExistingSwapsDetailsAndStatus(swapIds);
|
|
4612
|
+
if (result.result) {
|
|
4613
|
+
const extendedSwaps = [];
|
|
4614
|
+
for (let swap of result.swaps) {
|
|
4615
|
+
if (swap.status === SwapProvider.SWAP_STATUSES.REFUNDED) {
|
|
4616
|
+
const rate = await swapProvider.getCoinToUSDTRate(swap.fromCoin);
|
|
4617
|
+
extendedSwaps.push(ExistingSwapWithFiatData.fromExistingSwap(swap, (rate == null ? void 0 : rate.rate) != null ? BigNumber(swap.fromAmount).times(rate.rate).toNumber() : null, (rate == null ? void 0 : rate.rate) != null ? BigNumber(swap.toAmount).times(rate.rate).toNumber() : null, "USD", FiatCurrenciesService.getCurrencyDecimalCountByCode("USD")));
|
|
4618
|
+
} else {
|
|
4619
|
+
const [fromCoinFiatRate, toConFiatRate] = await Promise.all([swapProvider.getCoinToUSDTRate(swap.fromCoin), swapProvider.getCoinToUSDTRate(swap.toCoin)]);
|
|
4620
|
+
extendedSwaps.push(ExistingSwapWithFiatData.fromExistingSwap(swap, (fromCoinFiatRate == null ? void 0 : fromCoinFiatRate.rate) != null ? BigNumber(swap.fromAmount).times(fromCoinFiatRate.rate).toNumber() : null, (toConFiatRate == null ? void 0 : toConFiatRate.rate) != null ? BigNumber(swap.toAmount).times(toConFiatRate.rate).toNumber() : null, "USD", FiatCurrenciesService.getCurrencyDecimalCountByCode("USD")));
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4623
|
+
result.swaps = extendedSwaps;
|
|
4624
|
+
}
|
|
4625
|
+
return result;
|
|
4626
|
+
} catch (e) {
|
|
4627
|
+
improveAndRethrow(e, "getExistingSwapsDetailsWithFiatAmounts");
|
|
4628
|
+
}
|
|
4629
|
+
}
|
|
4630
|
+
}
|
|
4631
|
+
|
|
4632
|
+
class PublicSwapService {
|
|
4633
|
+
constructor(apiKeysProxyUrl, cache) {
|
|
4634
|
+
this._swapProvider = new SwapspaceSwapProvider(apiKeysProxyUrl, cache, () => null, false);
|
|
4635
|
+
}
|
|
4636
|
+
async initialize() {
|
|
4637
|
+
try {
|
|
4638
|
+
await this._swapProvider.initialize();
|
|
4639
|
+
} catch (e) {
|
|
4640
|
+
Logger.logError(e, "PublicSwapService.initialize");
|
|
4641
|
+
}
|
|
4642
|
+
}
|
|
4643
|
+
async getAllSupportedCurrenciesListForPublicSwap() {
|
|
4644
|
+
const loggerSource = "getAllSupportedCurrenciesListForPublicSwap";
|
|
4645
|
+
try {
|
|
4646
|
+
var _result$coins;
|
|
4647
|
+
const result = await this._swapProvider.getAllSupportedCurrencies();
|
|
4648
|
+
if (result.reason === SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED) {
|
|
4649
|
+
SwapUtils.safeHandleRequestsLimitExceeding();
|
|
4650
|
+
return {
|
|
4651
|
+
result: false,
|
|
4652
|
+
reason: PublicSwapService.PUBLIC_SWAPS_COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED
|
|
4653
|
+
};
|
|
4654
|
+
}
|
|
4655
|
+
Logger.log(`Retrieved ${result == null || (_result$coins = result.coins) == null ? void 0 : _result$coins.length} supported currencies for swap`, loggerSource);
|
|
4656
|
+
return {
|
|
4657
|
+
result: true,
|
|
4658
|
+
coins: result.coins
|
|
4659
|
+
};
|
|
4660
|
+
} catch (e) {
|
|
4661
|
+
improveAndRethrow(e, "getDepositCurrenciesListForPublicSwap");
|
|
4662
|
+
}
|
|
4663
|
+
}
|
|
4664
|
+
async getDepositCurrenciesListForPublicSwap() {
|
|
4665
|
+
try {
|
|
4666
|
+
return await this._getCurrenciesListForPublicSwap(false);
|
|
4667
|
+
} catch (e) {
|
|
4668
|
+
improveAndRethrow(e, "getDepositCurrenciesListForPublicSwap");
|
|
4669
|
+
}
|
|
4670
|
+
}
|
|
4671
|
+
async getWithdrawCurrenciesListForPublicSwap() {
|
|
4672
|
+
try {
|
|
4673
|
+
return await this._getCurrenciesListForPublicSwap(true);
|
|
4674
|
+
} catch (e) {
|
|
4675
|
+
improveAndRethrow(e, "getWithdrawCurrenciesListForPublicSwap");
|
|
4676
|
+
}
|
|
4677
|
+
}
|
|
4678
|
+
async _getCurrenciesListForPublicSwap(withdraw = false) {
|
|
4679
|
+
const loggerSource = "getCurrenciesListForPublicSwap";
|
|
4680
|
+
try {
|
|
4681
|
+
var _result$coins2;
|
|
4682
|
+
const result = withdraw ? await this._swapProvider.getWithdrawalCurrencies() : await this._swapProvider.getDepositCurrencies();
|
|
4683
|
+
if (result.reason === SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED) {
|
|
4684
|
+
SwapUtils.safeHandleRequestsLimitExceeding();
|
|
4685
|
+
return {
|
|
4686
|
+
result: false,
|
|
4687
|
+
reason: PublicSwapService.PUBLIC_SWAPS_COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED
|
|
4688
|
+
};
|
|
4689
|
+
}
|
|
4690
|
+
Logger.log(`Retrieved ${result == null || (_result$coins2 = result.coins) == null ? void 0 : _result$coins2.length} supported currencies for swap (withdraw=${withdraw})`, loggerSource);
|
|
4691
|
+
return {
|
|
4692
|
+
result: true,
|
|
4693
|
+
coins: result.coins
|
|
4694
|
+
};
|
|
4695
|
+
} catch (e) {
|
|
4696
|
+
improveAndRethrow(e, loggerSource);
|
|
4697
|
+
}
|
|
4698
|
+
}
|
|
4699
|
+
|
|
4700
|
+
/**
|
|
4701
|
+
* Retrieves initial data for swapping two coins.
|
|
4702
|
+
*
|
|
4703
|
+
* @param fromCoin {Coin}
|
|
4704
|
+
* @param toCoin {Coin}
|
|
4705
|
+
* @return {Promise<{
|
|
4706
|
+
* result: true,
|
|
4707
|
+
* min: string,
|
|
4708
|
+
* fiatMin: (number|null),
|
|
4709
|
+
* max: string,
|
|
4710
|
+
* fiatMax: (number|null),
|
|
4711
|
+
* rate: (string|null)
|
|
4712
|
+
* }|{
|
|
4713
|
+
* result: false,
|
|
4714
|
+
* reason: string
|
|
4715
|
+
* }>}
|
|
4716
|
+
*/
|
|
4717
|
+
async getInitialPublicSwapData(fromCoin, toCoin) {
|
|
4718
|
+
try {
|
|
4719
|
+
const result = await SwapUtils.getInitialSwapData(this._swapProvider, fromCoin, toCoin);
|
|
4720
|
+
if (!result.result) {
|
|
4721
|
+
if (result.reason === SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED) {
|
|
4722
|
+
SwapUtils.safeHandleRequestsLimitExceeding();
|
|
4723
|
+
return {
|
|
4724
|
+
result: false,
|
|
4725
|
+
reason: PublicSwapService.PUBLIC_SWAPS_COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED
|
|
4726
|
+
};
|
|
4727
|
+
}
|
|
4728
|
+
if (result.reason === SwapProvider.NO_SWAPS_REASONS.NOT_SUPPORTED) {
|
|
4729
|
+
return {
|
|
4730
|
+
result: false,
|
|
4731
|
+
reason: PublicSwapService.PUBLIC_SWAP_DETAILS_FAIL_REASONS.PAIR_NOT_SUPPORTED
|
|
4732
|
+
};
|
|
4733
|
+
}
|
|
4734
|
+
}
|
|
4735
|
+
return result;
|
|
4736
|
+
} catch (e) {
|
|
4737
|
+
improveAndRethrow(e, "getInitialPublicSwapData");
|
|
4738
|
+
}
|
|
4739
|
+
}
|
|
4740
|
+
|
|
4741
|
+
/**
|
|
4742
|
+
* Retrieves swap details that can be used to create swap.
|
|
4743
|
+
*
|
|
4744
|
+
* @param fromCoin {Coin}
|
|
4745
|
+
* @param toCoin {Coin}
|
|
4746
|
+
* @param fromAmountCoins {string}
|
|
4747
|
+
* @param [withoutFiat=false] {boolean} pass true if you don't need the fiat equivalent - this will diminish requests count
|
|
4748
|
+
* @return {Promise<{
|
|
4749
|
+
* result: false,
|
|
4750
|
+
* reason: string,
|
|
4751
|
+
* min: (string|null),
|
|
4752
|
+
* max: (string|null),
|
|
4753
|
+
* rate: (string|undefined),
|
|
4754
|
+
* fiatMin: (number|null),
|
|
4755
|
+
* fiatMax: (number|null)
|
|
4756
|
+
* }|{
|
|
4757
|
+
* result: true,
|
|
4758
|
+
* swapCreationInfo: BaseSwapCreationInfo
|
|
4759
|
+
* }>}
|
|
4760
|
+
*/
|
|
4761
|
+
async getPublicSwapDetails(fromCoin, toCoin, fromAmountCoins, withoutFiat = false) {
|
|
4762
|
+
const loggerSource = "getPublicSwapDetails";
|
|
4763
|
+
try {
|
|
4764
|
+
var _await$this$_swapProv, _await$this$_swapProv2, _result$swapCreationI, _result$swapCreationI2;
|
|
4765
|
+
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);
|
|
4767
|
+
const min = details.result ? details.min : details.smallestMin;
|
|
4768
|
+
const max = details.result ? details.max : details.greatestMax;
|
|
4769
|
+
let fiatMin = null,
|
|
4770
|
+
fiatMax = null;
|
|
4771
|
+
if (coinUsdtRate != null) {
|
|
4772
|
+
if (min != null) {
|
|
4773
|
+
fiatMin = BigNumber(min).times(coinUsdtRate).toFixed(PublicSwapService._fiatDecimalsCount);
|
|
4774
|
+
}
|
|
4775
|
+
if (max != null) {
|
|
4776
|
+
fiatMax = BigNumber(max).times(coinUsdtRate).toFixed(PublicSwapService._fiatDecimalsCount);
|
|
4777
|
+
}
|
|
4778
|
+
}
|
|
4779
|
+
const composeFailResult = reason => {
|
|
4780
|
+
var _details$rate;
|
|
4781
|
+
return {
|
|
4782
|
+
result: false,
|
|
4783
|
+
reason: reason,
|
|
4784
|
+
min: min != null ? min : null,
|
|
4785
|
+
fiatMin: fiatMin,
|
|
4786
|
+
max: max != null ? max : null,
|
|
4787
|
+
fiatMax: fiatMax,
|
|
4788
|
+
rate: (_details$rate = details.rate) != null ? _details$rate : null
|
|
4789
|
+
};
|
|
4790
|
+
};
|
|
4791
|
+
if (!details.result) {
|
|
4792
|
+
if ((details == null ? void 0 : details.reason) === SwapProvider.NO_SWAPS_REASONS.NOT_SUPPORTED) return composeFailResult(PublicSwapService.PUBLIC_SWAP_DETAILS_FAIL_REASONS.PAIR_NOT_SUPPORTED);else if ((details == null ? void 0 : details.reason) === SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED) {
|
|
4793
|
+
SwapUtils.safeHandleRequestsLimitExceeding();
|
|
4794
|
+
return composeFailResult(PublicSwapService.PUBLIC_SWAPS_COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED);
|
|
4795
|
+
}
|
|
4796
|
+
}
|
|
4797
|
+
const fromAmountBigNumber = BigNumber(fromAmountCoins);
|
|
4798
|
+
if (typeof min === "string" && fromAmountBigNumber.lt(min)) {
|
|
4799
|
+
return composeFailResult(PublicSwapService.PUBLIC_SWAP_DETAILS_FAIL_REASONS.AMOUNT_LESS_THAN_MIN_SWAPPABLE);
|
|
4800
|
+
} else if (typeof max === "string" && fromAmountBigNumber.gt(max)) {
|
|
4801
|
+
return composeFailResult(PublicSwapService.PUBLIC_SWAP_DETAILS_FAIL_REASONS.AMOUNT_HIGHER_THAN_MAX_SWAPPABLE);
|
|
4802
|
+
}
|
|
4803
|
+
const toAmountCoins = AmountUtils.trim(fromAmountBigNumber.times(details.rate), fromCoin.digits);
|
|
4804
|
+
const result = {
|
|
4805
|
+
result: true,
|
|
4806
|
+
swapCreationInfo: new BaseSwapCreationInfo(fromCoin, toCoin, fromAmountCoins, toAmountCoins, details.rate, details.rawSwapData, min, fiatMin, max, fiatMax, details.durationMinutesRange)
|
|
4807
|
+
};
|
|
4808
|
+
Logger.log(`Result: ${safeStringify({
|
|
4809
|
+
result: result.result,
|
|
4810
|
+
swapCreationInfo: _extends({}, result.swapCreationInfo, {
|
|
4811
|
+
fromCoin: result == null || (_result$swapCreationI = result.swapCreationInfo) == null || (_result$swapCreationI = _result$swapCreationI.fromCoin) == null ? void 0 : _result$swapCreationI.ticker,
|
|
4812
|
+
toCoin: result == null || (_result$swapCreationI2 = result.swapCreationInfo) == null || (_result$swapCreationI2 = _result$swapCreationI2.toCoin) == null ? void 0 : _result$swapCreationI2.ticker
|
|
4813
|
+
})
|
|
4814
|
+
})}`, loggerSource);
|
|
4815
|
+
return result;
|
|
4816
|
+
} catch (e) {
|
|
4817
|
+
improveAndRethrow(e, loggerSource);
|
|
4818
|
+
}
|
|
4819
|
+
}
|
|
4820
|
+
|
|
4821
|
+
/**
|
|
4822
|
+
* Creates swap by given params.
|
|
4823
|
+
*
|
|
4824
|
+
* @param fromCoin {Coin}
|
|
4825
|
+
* @param toCoin {Coin}
|
|
4826
|
+
* @param fromAmount {string}
|
|
4827
|
+
* @param swapCreationInfo {BaseSwapCreationInfo}
|
|
4828
|
+
* @param toAddress {string}
|
|
4829
|
+
* @param refundAddress {string}
|
|
4830
|
+
* @param clientIp {string}
|
|
4831
|
+
* @param [toCurrencyExtraId] {string}
|
|
4832
|
+
* @param [refundExtraId] {string}
|
|
4833
|
+
* @return {Promise<{
|
|
4834
|
+
* result: true,
|
|
4835
|
+
* fiatCurrencyCode: string,
|
|
4836
|
+
* toCoin: Coin,
|
|
4837
|
+
* fromAmountFiat: (number|null),
|
|
4838
|
+
* address: string,
|
|
4839
|
+
* durationMinutesRange: string,
|
|
4840
|
+
* fromAmount: string,
|
|
4841
|
+
* toAmount: string,
|
|
4842
|
+
* toAmountFiat: (number|null),
|
|
4843
|
+
* fiatCurrencyDecimals: number,
|
|
4844
|
+
* fromCoin: Coin,
|
|
4845
|
+
* rate: string,
|
|
4846
|
+
* swapId: string,
|
|
4847
|
+
* fromCurrencyExtraId: string
|
|
4848
|
+
* }|{
|
|
4849
|
+
* result: false,
|
|
4850
|
+
* reason: string
|
|
4851
|
+
* }>}
|
|
4852
|
+
*/
|
|
4853
|
+
async createPublicSwap(fromCoin, toCoin, fromAmount, swapCreationInfo, toAddress, refundAddress, clientIp, toCurrencyExtraId, refundExtraId) {
|
|
4854
|
+
const loggerSource = "createPublicSwap";
|
|
4855
|
+
try {
|
|
4856
|
+
var _swapCreationInfo$fro, _swapCreationInfo$toC;
|
|
4857
|
+
if (!(fromCoin instanceof Coin) || !(toCoin instanceof Coin) || typeof fromAmount !== "string" || typeof toAddress !== "string" || typeof refundAddress !== "string" || !(swapCreationInfo instanceof BaseSwapCreationInfo)) {
|
|
4858
|
+
throw new Error(`Wrong input: ${fromCoin.ticker} ${toCoin.ticker} ${fromAmount} ${swapCreationInfo}`);
|
|
4859
|
+
}
|
|
4860
|
+
Logger.log(`Start: ${fromAmount} ${fromCoin.ticker} -> ${toCoin.ticker}. Details: ${safeStringify(_extends({}, swapCreationInfo, {
|
|
4861
|
+
fromCoin: swapCreationInfo == null || (_swapCreationInfo$fro = swapCreationInfo.fromCoin) == null ? void 0 : _swapCreationInfo$fro.ticker,
|
|
4862
|
+
toCoin: swapCreationInfo == null || (_swapCreationInfo$toC = swapCreationInfo.toCoin) == null ? void 0 : _swapCreationInfo$toC.ticker
|
|
4863
|
+
}))}`, loggerSource);
|
|
4864
|
+
const result = await this._swapProvider.createSwap(fromCoin, toCoin, fromAmount, toAddress, refundAddress, swapCreationInfo.rawSwapData, clientIp, toCurrencyExtraId, refundExtraId);
|
|
4865
|
+
Logger.log(`Created:${safeStringify(_extends({}, result, {
|
|
4866
|
+
fromCoin: fromCoin == null ? void 0 : fromCoin.ticker,
|
|
4867
|
+
toCoin: toCoin == null ? void 0 : toCoin.ticker
|
|
4868
|
+
}))}`, loggerSource);
|
|
4869
|
+
if (!(result != null && result.result)) {
|
|
4870
|
+
if ((result == null ? void 0 : result.reason) === SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED) {
|
|
4871
|
+
SwapUtils.safeHandleRequestsLimitExceeding();
|
|
4872
|
+
return {
|
|
4873
|
+
result: false,
|
|
4874
|
+
reason: PublicSwapService.PUBLIC_SWAPS_COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED
|
|
4875
|
+
};
|
|
4876
|
+
}
|
|
4877
|
+
if ((result == null ? void 0 : result.reason) === SwapProvider.CREATION_FAIL_REASONS.RETRIABLE_FAIL) {
|
|
4878
|
+
// TODO: [feature, high] implement retrying if one partner fail and we have another partners task_id=a07e367e488f4a4899613ac9056fa359
|
|
4879
|
+
// return {
|
|
4880
|
+
// result: false,
|
|
4881
|
+
// reason: PublicSwapService.SWAP_CREATION_FAIL_REASONS.RETRIABLE_FAIL,
|
|
4882
|
+
// };
|
|
4883
|
+
}
|
|
4884
|
+
}
|
|
4885
|
+
if (result.result && result != null && result.swapId) {
|
|
4886
|
+
var _result$fromCurrencyE;
|
|
4887
|
+
let fromAmountFiat = null,
|
|
4888
|
+
toAmountFiat = null;
|
|
4889
|
+
try {
|
|
4890
|
+
var _await$this$_swapProv3, _await$this$_swapProv4, _await$this$_swapProv5, _await$this$_swapProv6;
|
|
4891
|
+
const fromCoinUsdtRate = (_await$this$_swapProv3 = (_await$this$_swapProv4 = await this._swapProvider.getCoinToUSDTRate(fromCoin)) == null ? void 0 : _await$this$_swapProv4.rate) != null ? _await$this$_swapProv3 : null;
|
|
4892
|
+
const toCoinUsdtRate = (_await$this$_swapProv5 = (_await$this$_swapProv6 = await this._swapProvider.getCoinToUSDTRate(fromCoin)) == null ? void 0 : _await$this$_swapProv6.rate) != null ? _await$this$_swapProv5 : null;
|
|
4893
|
+
if (fromCoinUsdtRate != null && result.fromAmount != null) {
|
|
4894
|
+
fromAmountFiat = BigNumber(result.fromAmount).times(fromCoinUsdtRate).toFixed(PublicSwapService._fiatDecimalsCount);
|
|
4895
|
+
}
|
|
4896
|
+
if (toCoinUsdtRate != null && result.toAmount != null) {
|
|
4897
|
+
toAmountFiat = BigNumber(result.toAmount).times(toCoinUsdtRate).toFixed(PublicSwapService._fiatDecimalsCount);
|
|
4898
|
+
}
|
|
4899
|
+
} catch (e) {
|
|
4900
|
+
Logger.logError(e, loggerSource, "Failed to calculate fiat amounts for result");
|
|
4901
|
+
}
|
|
4902
|
+
EventBusInstance.dispatch(PublicSwapService.PUBLIC_SWAP_CREATED_EVENT, null, fromCoin.ticker, toCoin.ticker, fromAmountFiat);
|
|
4903
|
+
const toReturn = {
|
|
4904
|
+
result: true,
|
|
4905
|
+
swapId: result.swapId,
|
|
4906
|
+
fromCoin: fromCoin,
|
|
4907
|
+
toCoin: toCoin,
|
|
4908
|
+
fromAmount: result.fromAmount,
|
|
4909
|
+
toAmount: result.toAmount,
|
|
4910
|
+
fromAmountFiat: fromAmountFiat,
|
|
4911
|
+
toAmountFiat: toAmountFiat,
|
|
4912
|
+
fiatCurrencyCode: "USD",
|
|
4913
|
+
fiatCurrencyDecimals: PublicSwapService._fiatDecimalsCount,
|
|
4914
|
+
rate: result.rate,
|
|
4915
|
+
durationMinutesRange: swapCreationInfo.durationMinutesRange,
|
|
4916
|
+
address: result.fromAddress,
|
|
4917
|
+
// CRITICAL: this is the address to send coins to swaps provider
|
|
4918
|
+
fromCurrencyExtraId: (_result$fromCurrencyE = result.fromCurrencyExtraId) != null ? _result$fromCurrencyE : "" // CRITICAL: this is the extra ID for address to send coins to swaps provider
|
|
4919
|
+
};
|
|
4920
|
+
this._savePublicSwapIdLocally(result.swapId);
|
|
4921
|
+
Logger.log(`Returning: ${safeStringify(_extends({}, toReturn, {
|
|
4922
|
+
fromCoin: fromCoin == null ? void 0 : fromCoin.ticker,
|
|
4923
|
+
toCoin: toCoin == null ? void 0 : toCoin.ticker
|
|
4924
|
+
}))}`, loggerSource);
|
|
4925
|
+
return toReturn;
|
|
4926
|
+
}
|
|
4927
|
+
throw new Error(`Unexpected result from provider ${safeStringify(result)}`);
|
|
4928
|
+
} catch (e) {
|
|
4929
|
+
improveAndRethrow(e, loggerSource);
|
|
4930
|
+
}
|
|
4931
|
+
}
|
|
4932
|
+
|
|
4933
|
+
/**
|
|
4934
|
+
* Retrieves swap details and status for existing swaps by their ids.
|
|
4935
|
+
*
|
|
4936
|
+
* @param swapIds {string[]}
|
|
4937
|
+
* @return {Promise<{
|
|
4938
|
+
* result: true,
|
|
4939
|
+
* swaps: ExistingSwapWithFiatData[]
|
|
4940
|
+
* }|{
|
|
4941
|
+
* result: false,
|
|
4942
|
+
* reason: string
|
|
4943
|
+
* }>}
|
|
4944
|
+
* error reason is one of PUBLIC_SWAPS_COMMON_ERRORS
|
|
4945
|
+
*/
|
|
4946
|
+
async getPublicExistingSwapDetailsAndStatus(swapIds) {
|
|
4947
|
+
const loggerSource = "getPublicExistingSwapDetailsAndStatus";
|
|
4948
|
+
try {
|
|
4949
|
+
const result = await SwapUtils.getExistingSwapsDetailsWithFiatAmounts(this._swapProvider, swapIds);
|
|
4950
|
+
if (!(result != null && result.result)) {
|
|
4951
|
+
if (result.reason === SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED) {
|
|
4952
|
+
SwapUtils.safeHandleRequestsLimitExceeding();
|
|
4953
|
+
return {
|
|
4954
|
+
result: false,
|
|
4955
|
+
reason: PublicSwapService.PUBLIC_SWAPS_COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED
|
|
4956
|
+
};
|
|
4957
|
+
}
|
|
4958
|
+
throw new Error("Unknown reason: " + (result == null ? void 0 : result.reason));
|
|
4959
|
+
}
|
|
4960
|
+
return result;
|
|
4961
|
+
} catch (e) {
|
|
4962
|
+
improveAndRethrow(e, loggerSource);
|
|
4963
|
+
}
|
|
4964
|
+
}
|
|
4965
|
+
|
|
4966
|
+
/**
|
|
4967
|
+
* Retrieves the whole available swaps history by ids saved locally.
|
|
4968
|
+
*
|
|
4969
|
+
* @return {Promise<{
|
|
4970
|
+
* result: true,
|
|
4971
|
+
* swaps: ExistingSwapWithFiatData[]
|
|
4972
|
+
* }|{
|
|
4973
|
+
* result: false,
|
|
4974
|
+
* reason: string
|
|
4975
|
+
* }>}
|
|
4976
|
+
*/
|
|
4977
|
+
async getPublicSwapsHistory() {
|
|
4978
|
+
try {
|
|
4979
|
+
const swapIds = this._getPublicSwapIdsSavedLocally();
|
|
4980
|
+
if (swapIds.length) {
|
|
4981
|
+
return await this.getPublicExistingSwapDetailsAndStatus(swapIds);
|
|
4982
|
+
}
|
|
4983
|
+
return {
|
|
4984
|
+
result: true,
|
|
4985
|
+
swaps: []
|
|
4986
|
+
};
|
|
4987
|
+
} catch (e) {
|
|
4988
|
+
improveAndRethrow(e, "getPublicSwapsHistory");
|
|
4989
|
+
}
|
|
4990
|
+
}
|
|
4991
|
+
|
|
4992
|
+
/**
|
|
4993
|
+
* @param swapId {string}
|
|
4994
|
+
* @private
|
|
4995
|
+
*/
|
|
4996
|
+
_savePublicSwapIdLocally(swapId) {
|
|
4997
|
+
if (typeof window !== "undefined") {
|
|
4998
|
+
try {
|
|
4999
|
+
const saved = localStorage.getItem("publicSwapIds");
|
|
5000
|
+
const ids = typeof saved === "string" && saved.length > 0 ? saved.split(",") : [];
|
|
5001
|
+
ids.push(swapId);
|
|
5002
|
+
localStorage.setItem("publicSwapIds", ids.join(","));
|
|
5003
|
+
} catch (e) {
|
|
5004
|
+
improveAndRethrow(e, "_savePublicSwapIdLocally");
|
|
5005
|
+
}
|
|
5006
|
+
}
|
|
5007
|
+
}
|
|
5008
|
+
|
|
5009
|
+
/**
|
|
5010
|
+
* @private
|
|
5011
|
+
* @return {string[]}
|
|
5012
|
+
*/
|
|
5013
|
+
_getPublicSwapIdsSavedLocally() {
|
|
5014
|
+
if (typeof window !== "undefined") {
|
|
5015
|
+
try {
|
|
5016
|
+
const saved = localStorage.getItem("publicSwapIds");
|
|
5017
|
+
return typeof saved === "string" && saved.length > 0 ? saved.split(",") : [];
|
|
5018
|
+
} catch (e) {
|
|
5019
|
+
improveAndRethrow(e, "_getPublicSwapIdsSavedLocally");
|
|
5020
|
+
}
|
|
5021
|
+
}
|
|
5022
|
+
}
|
|
5023
|
+
|
|
5024
|
+
/**
|
|
5025
|
+
* @param coinOrTicker {Coin|string}
|
|
5026
|
+
* @return {string} icon URL (ready to use)
|
|
5027
|
+
*/
|
|
5028
|
+
getAssetIconUrl(coinOrTicker) {
|
|
5029
|
+
return this._swapProvider.getIconUrl(coinOrTicker);
|
|
5030
|
+
}
|
|
5031
|
+
|
|
5032
|
+
/**
|
|
5033
|
+
* @param ticker {string}
|
|
5034
|
+
* @return {Coin|null}
|
|
5035
|
+
*/
|
|
5036
|
+
getCoinByTickerIfPresent(ticker) {
|
|
5037
|
+
return this._swapProvider.getCoinByTickerIfPresent(ticker);
|
|
5038
|
+
}
|
|
5039
|
+
|
|
5040
|
+
/**
|
|
5041
|
+
* TODO: [feature, moderate] add other fiat currencies support. task_id=5490e21b8b9c4f89a2247b28db3c9e0a
|
|
5042
|
+
* @param asset {Coin}
|
|
5043
|
+
* @return {Promise<string|null>}
|
|
5044
|
+
*/
|
|
5045
|
+
async getAssetToUsdtRate(asset) {
|
|
5046
|
+
try {
|
|
5047
|
+
var _result$rate;
|
|
5048
|
+
const result = await this._swapProvider.getCoinToUSDTRate(asset);
|
|
5049
|
+
return (_result$rate = result == null ? void 0 : result.rate) != null ? _result$rate : null;
|
|
5050
|
+
} catch (e) {
|
|
5051
|
+
improveAndRethrow(e, "getAssetToUsdtRate");
|
|
5052
|
+
}
|
|
5053
|
+
}
|
|
5054
|
+
|
|
5055
|
+
/**
|
|
5056
|
+
* @param asset {Coin}
|
|
5057
|
+
* @param address {string}
|
|
5058
|
+
* @return {boolean}
|
|
5059
|
+
*/
|
|
5060
|
+
isAddressValidForAsset(asset, address) {
|
|
5061
|
+
try {
|
|
5062
|
+
return this._swapProvider.isAddressValidForAsset(asset, address);
|
|
5063
|
+
} catch (e) {
|
|
5064
|
+
improveAndRethrow(e, "isAddressValidForAsset");
|
|
5065
|
+
}
|
|
5066
|
+
}
|
|
5067
|
+
|
|
5068
|
+
/**
|
|
5069
|
+
* Retrieves token by contract address.
|
|
5070
|
+
*
|
|
5071
|
+
* @param addressString {string}
|
|
5072
|
+
* @return {Promise<Coin|null>}
|
|
5073
|
+
*/
|
|
5074
|
+
async getTokenByContractAddress(addressString) {
|
|
5075
|
+
try {
|
|
5076
|
+
if (!addressString) return null;
|
|
5077
|
+
const addressLowerCase = addressString.toLowerCase();
|
|
5078
|
+
const allCoins = await this._swapProvider.getAllSupportedCurrencies();
|
|
5079
|
+
if (allCoins.result) {
|
|
5080
|
+
return allCoins.coins.find(coin => coin.tokenAddress && coin.tokenAddress.toLowerCase() === addressLowerCase);
|
|
5081
|
+
}
|
|
5082
|
+
} catch (e) {
|
|
5083
|
+
Logger.logError(e, "getTokenByContractAddress");
|
|
5084
|
+
}
|
|
5085
|
+
return null;
|
|
5086
|
+
}
|
|
5087
|
+
|
|
5088
|
+
/**
|
|
5089
|
+
* @param asset {Coin}
|
|
5090
|
+
* @return {string|null}
|
|
5091
|
+
*/
|
|
5092
|
+
getExtraIdNameIfPresentForAsset(asset) {
|
|
5093
|
+
try {
|
|
5094
|
+
return this._swapProvider.getExtraIdNameIfPresent(asset);
|
|
5095
|
+
} catch (e) {
|
|
5096
|
+
improveAndRethrow(e, "getExtraIdNameIfPresentForAsset");
|
|
5097
|
+
}
|
|
5098
|
+
}
|
|
5099
|
+
}
|
|
5100
|
+
PublicSwapService.PUBLIC_SWAP_CREATED_EVENT = "publicSwapCreatedEvent";
|
|
5101
|
+
PublicSwapService.PUBLIC_SWAPS_COMMON_ERRORS = {
|
|
5102
|
+
REQUESTS_LIMIT_EXCEEDED: "requestsLimitExceeded"
|
|
5103
|
+
};
|
|
5104
|
+
PublicSwapService.PUBLIC_SWAP_DETAILS_FAIL_REASONS = {
|
|
5105
|
+
AMOUNT_LESS_THAN_MIN_SWAPPABLE: "amountLessThanMinSwappable",
|
|
5106
|
+
AMOUNT_HIGHER_THAN_MAX_SWAPPABLE: "amountHigherThanMaxSwappable",
|
|
5107
|
+
PAIR_NOT_SUPPORTED: "pairNotSupported"
|
|
5108
|
+
};
|
|
5109
|
+
PublicSwapService._fiatDecimalsCount = FiatCurrenciesService.getCurrencyDecimalCountByCode("USD");
|
|
5110
|
+
|
|
5111
|
+
export { AmountUtils, ApiGroup, ApiGroups, AssetIcon, AxiosAdapter, BaseSwapCreationInfo, Blockchain, Button, Cache, CacheAndConcurrentRequestsResolver, CachedRobustExternalApiCallerService, CancelProcessing, Coin, 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 };
|
|
1357
5112
|
//# sourceMappingURL=index.modern.js.map
|