diff options
author | Alex Browne <stephenalexbrowne@gmail.com> | 2018-08-14 09:42:09 +0800 |
---|---|---|
committer | Alex Browne <stephenalexbrowne@gmail.com> | 2018-08-14 09:42:09 +0800 |
commit | 88766a02c7e6688e72d5c4c69ce68028b322f154 (patch) | |
tree | fa06552a80249e7998691b64df6b3b2827f9f947 /packages/website/ts/utils/utils.ts | |
parent | 8162394797342cef268cc8072fc860326974e269 (diff) | |
parent | fadd292ecf367e42154856509d0ea0c20b23f2f1 (diff) | |
download | dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.tar dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.tar.gz dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.tar.bz2 dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.tar.lz dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.tar.xz dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.tar.zst dexon-0x-contracts-88766a02c7e6688e72d5c4c69ce68028b322f154.zip |
Merge branch 'development'
Diffstat (limited to 'packages/website/ts/utils/utils.ts')
-rw-r--r-- | packages/website/ts/utils/utils.ts | 310 |
1 files changed, 251 insertions, 59 deletions
diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts index 75597a7e2..39bbd404c 100644 --- a/packages/website/ts/utils/utils.ts +++ b/packages/website/ts/utils/utils.ts @@ -1,28 +1,42 @@ -import { ECSignature, ExchangeContractErrs, Web3Provider, ZeroEx, ZeroExError } from '0x.js'; -import { constants as sharedConstants, EtherscanLinkSuffixes, Networks } from '@0xproject/react-shared'; +import { ContractWrappersError, ExchangeContractErrs } from '@0xproject/contract-wrappers'; +import { OrderError } from '@0xproject/order-utils'; +import { constants as sharedConstants, Networks } from '@0xproject/react-shared'; +import { ECSignature, Provider } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import * as bowser from 'bowser'; import deepEqual = require('deep-equal'); import * as _ from 'lodash'; import * as moment from 'moment'; -import { Order, Providers, ScreenWidths, Side, SideToAssetToken, Token, TokenByAddress } from 'ts/types'; +import * as numeral from 'numeral'; + +import { + AccountState, + BlockchainCallErrs, + BrowserType, + Environments, + OperatingSystemType, + Order, + Providers, + ProviderType, + ScreenWidths, + Side, + SideToAssetToken, + Token, + TokenByAddress, + TokenState, +} from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import * as u2f from 'ts/vendor/u2f_api'; -import Web3 = require('web3'); - -const LG_MIN_EM = 64; -const MD_MIN_EM = 52; export const utils = { - assert(condition: boolean, message: string) { + assert(condition: boolean, message: string): void { if (!condition) { throw new Error(message); } }, - spawnSwitchErr(name: string, value: any) { - return new Error(`Unexpected switch value: ${value} encountered for ${name}`); - }, - isNumeric(n: string) { + isNumeric(n: string): boolean { return !isNaN(parseFloat(n)) && isFinite(Number(n)); }, // This default unix timestamp is used for orders where the user does not specify an expiry date. @@ -45,7 +59,7 @@ export const utils = { return moment.unix(unixTimestampSec.toNumber()); }, convertToReadableDateTimeFromUnixTimestamp(unixTimestampSec: BigNumber): string { - const m = this.convertToMomentFromUnixTimestamp(unixTimestampSec); + const m = utils.convertToMomentFromUnixTimestamp(unixTimestampSec); const formattedDate: string = m.format('h:MMa MMMM D YYYY'); return formattedDate; }, @@ -95,13 +109,13 @@ export const utils = { }; return order; }, - async sleepAsync(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); + async sleepAsync(ms: number): Promise<NodeJS.Timer> { + return new Promise<NodeJS.Timer>(resolve => setTimeout(resolve, ms)); }, - deepEqual(actual: any, expected: any, opts?: { strict: boolean }) { + deepEqual(actual: any, expected: any, opts?: { strict: boolean }): boolean { return deepEqual(actual, expected, opts); }, - getColSize(items: number) { + getColSize(items: number): number { const bassCssGridSize = 12; // Source: http://basscss.com/#basscss-grid const colSize = bassCssGridSize / items; if (!_.isInteger(colSize)) { @@ -109,7 +123,7 @@ export const utils = { } return colSize; }, - getScreenWidth() { + getScreenWidth(): ScreenWidths { const documentEl = document.documentElement; const body = document.getElementsByTagName('body')[0]; const widthInPx = window.innerWidth || documentEl.clientWidth || body.clientWidth; @@ -118,9 +132,9 @@ export const utils = { // This logic mirrors the CSS media queries in BassCSS for the `lg-`, `md-` and `sm-` CSS // class prefixes. Do not edit these. - if (widthInEm > LG_MIN_EM) { + if (widthInEm > ScreenWidths.Lg) { return ScreenWidths.Lg; - } else if (widthInEm > MD_MIN_EM) { + } else if (widthInEm > ScreenWidths.Md) { return ScreenWidths.Md; } else { return ScreenWidths.Sm; @@ -141,7 +155,7 @@ export const utils = { const intervalId = setTimeout(() => { resolve(false); }, getApiVersionTimeoutMs); - u2f.getApiVersion((version: number) => { + u2f.getApiVersion((_version: number) => { clearTimeout(intervalId); resolve(true); }); @@ -151,7 +165,7 @@ export const utils = { // This checks the error message returned from an injected Web3 instance on the page // after a user was prompted to sign a message or send a transaction and decided to // reject the request. - didUserDenyWeb3Request(errMsg: string) { + didUserDenyWeb3Request(errMsg: string): boolean { const metamaskDenialErrMsg = 'User denied'; const paritySignerDenialErrMsg = 'Request has been rejected'; const ledgerDenialErrMsg = 'Invalid status 6985'; @@ -161,23 +175,44 @@ export const utils = { _.includes(errMsg, ledgerDenialErrMsg); return isUserDeniedErrMsg; }, - getCurrentEnvironment() { - switch (location.host) { - case configs.DOMAIN_DEVELOPMENT: - return 'development'; - case configs.DOMAIN_STAGING: - return 'staging'; - case configs.DOMAIN_PRODUCTION: - return 'production'; - default: - return 'production'; - } - }, getAddressBeginAndEnd(address: string): string { const truncatedAddress = `${address.substring(0, 6)}...${address.substr(-4)}`; // 0x3d5a...b287 return truncatedAddress; }, - hasUniqueNameAndSymbol(tokens: Token[], token: Token) { + getReadableAccountState(accountState: AccountState, userAddress: string): string { + switch (accountState) { + case AccountState.Loading: + return 'Loading...'; + case AccountState.Ready: + return utils.getAddressBeginAndEnd(userAddress); + case AccountState.Locked: + return 'Please Unlock'; + case AccountState.Disconnected: + return 'Connect a Wallet'; + default: + return ''; + } + }, + getAccountState( + isBlockchainReady: boolean, + providerType: ProviderType, + injectedProviderName: string, + userAddress?: string, + ): AccountState { + const isAddressAvailable = !_.isUndefined(userAddress) && !_.isEmpty(userAddress); + const isExternallyInjectedProvider = utils.isExternallyInjected(providerType, injectedProviderName); + if (!isBlockchainReady) { + return AccountState.Loading; + } else if (isAddressAvailable) { + return AccountState.Ready; + // tslint:disable-next-line: prefer-conditional-expression + } else if (isExternallyInjectedProvider) { + return AccountState.Locked; + } else { + return AccountState.Disconnected; + } + }, + hasUniqueNameAndSymbol(tokens: Token[], token: Token): boolean { if (token.isRegistered) { return true; // Since it's registered, it is the canonical token } @@ -192,21 +227,20 @@ export const utils = { const isUniqueSymbol = _.isUndefined(tokenWithSameSymbolIfExists); return isUniqueName && isUniqueSymbol; }, - zeroExErrToHumanReadableErrMsg(error: ZeroExError | ExchangeContractErrs, takerAddress: string): string { - const ZeroExErrorToHumanReadableError: { [error: string]: string } = { - [ZeroExError.ExchangeContractDoesNotExist]: 'Exchange contract does not exist', - [ZeroExError.EtherTokenContractDoesNotExist]: 'EtherToken contract does not exist', - [ZeroExError.TokenTransferProxyContractDoesNotExist]: 'TokenTransferProxy contract does not exist', - [ZeroExError.TokenRegistryContractDoesNotExist]: 'TokenRegistry contract does not exist', - [ZeroExError.TokenContractDoesNotExist]: 'Token contract does not exist', - [ZeroExError.ZRXContractDoesNotExist]: 'ZRX contract does not exist', - [ZeroExError.UnhandledError]: 'Unhandled error occured', - [ZeroExError.UserHasNoAssociatedAddress]: 'User has no addresses available', - [ZeroExError.InvalidSignature]: 'Order signature is not valid', - [ZeroExError.ContractNotDeployedOnNetwork]: 'Contract is not deployed on the detected network', - [ZeroExError.InvalidJump]: 'Invalid jump occured while executing the transaction', - [ZeroExError.OutOfGas]: 'Transaction ran out of gas', - [ZeroExError.NoNetworkId]: 'No network id detected', + zeroExErrToHumanReadableErrMsg(error: ContractWrappersError | ExchangeContractErrs, takerAddress: string): string { + const ContractWrappersErrorToHumanReadableError: { [error: string]: string } = { + [ContractWrappersError.ExchangeContractDoesNotExist]: 'Exchange contract does not exist', + [ContractWrappersError.EtherTokenContractDoesNotExist]: 'EtherToken contract does not exist', + [ContractWrappersError.TokenTransferProxyContractDoesNotExist]: + 'TokenTransferProxy contract does not exist', + [ContractWrappersError.TokenRegistryContractDoesNotExist]: 'TokenRegistry contract does not exist', + [ContractWrappersError.TokenContractDoesNotExist]: 'Token contract does not exist', + [ContractWrappersError.ZRXContractDoesNotExist]: 'ZRX contract does not exist', + [BlockchainCallErrs.UserHasNoAssociatedAddresses]: 'User has no addresses available', + [OrderError.InvalidSignature]: 'Order signature is not valid', + [ContractWrappersError.ContractNotDeployedOnNetwork]: 'Contract is not deployed on the detected network', + [ContractWrappersError.InvalidJump]: 'Invalid jump occured while executing the transaction', + [ContractWrappersError.OutOfGas]: 'Transaction ran out of gas', }; const exchangeContractErrorToHumanReadableError: { [error: string]: string; @@ -239,7 +273,7 @@ export const utils = { [ExchangeContractErrs.InsufficientRemainingFillAmount]: 'Insufficient remaining fill amount', }; const humanReadableErrorMsg = - exchangeContractErrorToHumanReadableError[error] || ZeroExErrorToHumanReadableError[error]; + exchangeContractErrorToHumanReadableError[error] || ContractWrappersErrorToHumanReadableError[error]; return humanReadableErrorMsg; }, isParityNode(nodeVersion: string): boolean { @@ -259,23 +293,23 @@ export const utils = { ); return isTestNetwork; }, - getCurrentBaseUrl() { + getCurrentBaseUrl(): string { const port = window.location.port; const hasPort = !_.isUndefined(port); const baseUrl = `https://${window.location.hostname}${hasPort ? `:${port}` : ''}`; return baseUrl; }, - async onPageLoadAsync(): Promise<void> { + onPageLoadPromise: new Promise<void>((resolve, _reject) => { if (document.readyState === 'complete') { - return; // Already loaded + resolve(); + return; } - return new Promise<void>((resolve, reject) => { - window.onload = () => resolve(); - }); - }, - getProviderType(provider: Web3.Provider): Providers | string { + window.onload = () => resolve(); + }), + getProviderType(provider: Provider): Providers | string { const constructorName = provider.constructor.name; let parsedProviderName = constructorName; + // https://ethereum.stackexchange.com/questions/24266/elegant-way-to-detect-current-provider-int-web3-js switch (constructorName) { case 'EthereumProvider': parsedProviderName = Providers.Mist; @@ -289,7 +323,165 @@ export const utils = { parsedProviderName = Providers.Parity; } else if ((provider as any).isMetaMask) { parsedProviderName = Providers.Metamask; + } else if (!_.isUndefined(_.get(window, 'SOFA'))) { + parsedProviderName = Providers.Toshi; + } else if (!_.isUndefined(_.get(window, '__CIPHER__'))) { + parsedProviderName = Providers.Cipher; } return parsedProviderName; }, + getBackendBaseUrl(): string { + return utils.isDogfood() ? configs.BACKEND_BASE_STAGING_URL : configs.BACKEND_BASE_PROD_URL; + }, + isDevelopment(): boolean { + return _.includes(configs.DOMAINS_DEVELOPMENT, window.location.host); + }, + isStaging(): boolean { + return _.includes(window.location.href, configs.DOMAIN_STAGING); + }, + isExternallyInjected(providerType: ProviderType, injectedProviderName: string): boolean { + return providerType === ProviderType.Injected && injectedProviderName !== constants.PROVIDER_NAME_PUBLIC; + }, + isDogfood(): boolean { + return _.includes(window.location.href, configs.DOMAIN_DOGFOOD); + }, + isProduction(): boolean { + return _.includes(window.location.href, configs.DOMAIN_PRODUCTION); + }, + getEnvironment(): Environments { + if (utils.isDogfood()) { + return Environments.DOGFOOD; + } + if (utils.isDevelopment()) { + return Environments.DEVELOPMENT; + } + if (utils.isStaging()) { + return Environments.STAGING; + } + if (utils.isProduction()) { + return Environments.PRODUCTION; + } + return Environments.UNKNOWN; + }, + getEthToken(tokenByAddress: TokenByAddress): Token { + return utils.getTokenBySymbol(constants.ETHER_TOKEN_SYMBOL, tokenByAddress); + }, + getZrxToken(tokenByAddress: TokenByAddress): Token { + return utils.getTokenBySymbol(constants.ZRX_TOKEN_SYMBOL, tokenByAddress); + }, + getTokenBySymbol(symbol: string, tokenByAddress: TokenByAddress): Token { + const tokens = _.values(tokenByAddress); + const token = _.find(tokens, { symbol }); + return token; + }, + getTrackedTokens(tokenByAddress: TokenByAddress): Token[] { + const allTokens = _.values(tokenByAddress); + const trackedTokens = _.filter(allTokens, t => utils.isTokenTracked(t)); + return trackedTokens; + }, + getFormattedAmountFromToken(token: Token, tokenState: TokenState): string { + return utils.getFormattedAmount(tokenState.balance, token.decimals); + }, + format(value: BigNumber, format: string): string { + const formattedAmount = numeral(value).format(format); + if (_.isNaN(formattedAmount)) { + // https://github.com/adamwdraper/Numeral-js/issues/596 + return numeral(new BigNumber(0)).format(format); + } + return formattedAmount; + }, + getFormattedAmount(amount: BigNumber, decimals: number): string { + const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals); + // if the unit amount is less than 1, show the natural number of decimal places with a max of 4 + // if the unit amount is greater than or equal to 1, show only 2 decimal places + const lessThanOnePrecision = Math.min(constants.TOKEN_AMOUNT_DISPLAY_PRECISION, unitAmount.decimalPlaces()); + const greaterThanOnePrecision = 2; + const precision = unitAmount.lt(1) ? lessThanOnePrecision : greaterThanOnePrecision; + const format = `0,0.${_.repeat('0', precision)}`; + return utils.format(unitAmount, format); + }, + getUsdValueFormattedAmount(amount: BigNumber, decimals: number, price: BigNumber): string { + const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals); + const value = unitAmount.mul(price); + return utils.format(value, constants.NUMERAL_USD_FORMAT); + }, + openUrl(url: string): void { + window.open(url, '_blank'); + }, + isMobileWidth(screenWidth: ScreenWidths): boolean { + return screenWidth === ScreenWidths.Sm; + }, + isMobileOperatingSystem(): boolean { + return bowser.mobile; + }, + getBrowserType(): BrowserType { + if (bowser.chrome) { + return BrowserType.Chrome; + } else if (bowser.firefox) { + return BrowserType.Firefox; + } else if (bowser.opera) { + return BrowserType.Opera; + } else { + return BrowserType.Other; + } + }, + getOperatingSystem(): OperatingSystemType { + if (bowser.android) { + return OperatingSystemType.Android; + } else if (bowser.ios) { + return OperatingSystemType.iOS; + } else if (bowser.mac) { + return OperatingSystemType.Mac; + } else if (bowser.windows) { + return OperatingSystemType.Windows; + } else if (bowser.windowsphone) { + return OperatingSystemType.WindowsPhone; + } else if (bowser.linux) { + return OperatingSystemType.Linux; + } else { + return OperatingSystemType.Other; + } + }, + isTokenTracked(token: Token): boolean { + return !_.isUndefined(token.trackedTimestamp); + }, + // Returns a [downloadLink, isOnMobile] tuple. + getBestWalletDownloadLinkAndIsMobile(): [string, boolean] { + const browserType = utils.getBrowserType(); + const isOnMobile = utils.isMobileOperatingSystem(); + const operatingSystem = utils.getOperatingSystem(); + let downloadLink; + if (isOnMobile) { + switch (operatingSystem) { + case OperatingSystemType.Android: + downloadLink = constants.URL_TOSHI_ANDROID_APP_STORE; + break; + case OperatingSystemType.iOS: + downloadLink = constants.URL_TOSHI_IOS_APP_STORE; + break; + default: + // Toshi is only supported on these mobile OSes - just default to iOS + downloadLink = constants.URL_TOSHI_IOS_APP_STORE; + } + } else { + switch (browserType) { + case BrowserType.Chrome: + downloadLink = constants.URL_METAMASK_CHROME_STORE; + break; + case BrowserType.Firefox: + downloadLink = constants.URL_METAMASK_FIREFOX_STORE; + break; + case BrowserType.Opera: + downloadLink = constants.URL_METAMASK_OPERA_STORE; + break; + default: + downloadLink = constants.URL_METAMASK_HOMEPAGE; + } + } + return [downloadLink, isOnMobile]; + }, + getTokenIconUrl(symbol: string): string { + const result = `/images/token_icons/${symbol}.png`; + return result; + }, }; |