diff options
author | fragosti <francesco.agosti93@gmail.com> | 2018-11-21 23:43:36 +0800 |
---|---|---|
committer | fragosti <francesco.agosti93@gmail.com> | 2018-11-21 23:43:36 +0800 |
commit | fc123871ad9b1e791b8156d069c503e8d869139c (patch) | |
tree | f31546c91d6936432f18d2a2c224549c7f65c9c9 /packages/instant | |
parent | b62fbd0b13dbef67d8df1177b1ca6b4d082faaa9 (diff) | |
parent | f27fef0295243eeb85498ee09810a2f56afd88bd (diff) | |
download | dexon-sol-tools-fc123871ad9b1e791b8156d069c503e8d869139c.tar.gz dexon-sol-tools-fc123871ad9b1e791b8156d069c503e8d869139c.tar.zst dexon-sol-tools-fc123871ad9b1e791b8156d069c503e8d869139c.zip |
Merge branch 'development' of https://github.com/0xProject/0x-monorepo into feature/instant/push-to-history
Diffstat (limited to 'packages/instant')
-rw-r--r-- | packages/instant/.npmignore | 2 | ||||
-rw-r--r-- | packages/instant/package.json | 7 | ||||
-rw-r--r-- | packages/instant/src/assets/powered_by_0x.svg | 17 | ||||
-rw-r--r-- | packages/instant/src/components/erc20_asset_amount_input.tsx | 12 | ||||
-rw-r--r-- | packages/instant/src/components/zero_ex_instant_container.tsx | 13 | ||||
-rw-r--r-- | packages/instant/src/components/zero_ex_instant_provider.tsx | 17 | ||||
-rw-r--r-- | packages/instant/src/constants.ts | 3 | ||||
-rw-r--r-- | packages/instant/src/containers/selected_erc20_asset_amount_input.ts | 18 | ||||
-rw-r--r-- | packages/instant/src/globals.d.ts | 6 | ||||
-rw-r--r-- | packages/instant/src/index.umd.ts | 3 | ||||
-rw-r--r-- | packages/instant/src/redux/analytics_middleware.ts | 59 | ||||
-rw-r--r-- | packages/instant/src/redux/store.ts | 7 | ||||
-rw-r--r-- | packages/instant/src/types.ts | 1 | ||||
-rw-r--r-- | packages/instant/src/util/analytics.ts | 64 | ||||
-rw-r--r-- | packages/instant/src/util/heap.ts | 113 | ||||
-rw-r--r-- | packages/instant/src/util/provider_state_factory.ts | 2 | ||||
-rw-r--r-- | packages/instant/tsconfig.json | 11 | ||||
-rw-r--r-- | packages/instant/webpack.config.js | 115 |
18 files changed, 401 insertions, 69 deletions
diff --git a/packages/instant/.npmignore b/packages/instant/.npmignore index 579d65af0..a4f7810c0 100644 --- a/packages/instant/.npmignore +++ b/packages/instant/.npmignore @@ -1,5 +1,5 @@ .* * */ -!lib/src/**/* +!lib/**/* !umd/**/*
\ No newline at end of file diff --git a/packages/instant/package.json b/packages/instant/package.json index 1813d61e5..69319e854 100644 --- a/packages/instant/package.json +++ b/packages/instant/package.json @@ -5,9 +5,8 @@ "node": ">=6.12" }, "description": "0x Instant React Component", - "private": true, - "main": "lib/src/index.js", - "types": "lib/src/index.d.ts", + "main": "lib/index.js", + "types": "lib/index.d.ts", "scripts": { "build": "yarn build:all", "build:all": "run-p build:umd:prod build:commonjs", @@ -99,6 +98,6 @@ "webpack-dev-server": "^3.1.9" }, "publishConfig": { - "access": "private" + "access": "public" } } diff --git a/packages/instant/src/assets/powered_by_0x.svg b/packages/instant/src/assets/powered_by_0x.svg new file mode 100644 index 000000000..e3d007d0b --- /dev/null +++ b/packages/instant/src/assets/powered_by_0x.svg @@ -0,0 +1,17 @@ +<svg width="108" height="18" viewBox="0 0 108 18" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M1.17959 10.0032V13.5455H0V4.49716H3.29917C4.27807 4.49716 5.04398 4.74988 5.59692 5.25533C6.15395 5.76077 6.43246 6.42986 6.43246 7.26261C6.43246 8.14092 6.16009 8.8183 5.61535 9.29474C5.0747 9.76705 4.29855 10.0032 3.28688 10.0032H1.17959ZM1.17959 9.02752H3.29917C3.92992 9.02752 4.41323 8.87837 4.74909 8.58008C5.08494 8.27764 5.25287 7.84263 5.25287 7.27504C5.25287 6.73645 5.08494 6.30558 4.74909 5.98242C4.41323 5.65927 3.95245 5.49148 3.36675 5.47905H1.17959V9.02752Z" fill="#777777"/> +<path d="M7.37245 10.1213C7.37245 9.46254 7.49942 8.87009 7.75336 8.34393C8.01139 7.81777 8.36773 7.41175 8.82236 7.12589C9.28109 6.84002 9.80331 6.69709 10.389 6.69709C11.2942 6.69709 12.0253 7.01403 12.5823 7.6479C13.1434 8.28178 13.424 9.12488 13.424 10.1772V10.258C13.424 10.9126 13.2991 11.5009 13.0492 12.0229C12.8035 12.5408 12.4492 12.9447 11.9864 13.2347C11.5276 13.5247 10.9993 13.6697 10.4013 13.6697C9.50022 13.6697 8.76912 13.3528 8.20799 12.7189C7.65096 12.085 7.37245 11.2461 7.37245 10.2021V10.1213ZM8.51518 10.258C8.51518 11.0037 8.68515 11.6024 9.02511 12.054C9.36915 12.5056 9.82788 12.7314 10.4013 12.7314C10.9788 12.7314 11.4375 12.5035 11.7775 12.0478C12.1174 11.5879 12.2874 10.9457 12.2874 10.1213C12.2874 9.38382 12.1133 8.78723 11.7652 8.3315C11.4212 7.87163 10.9624 7.64169 10.389 7.64169C9.82788 7.64169 9.3753 7.86748 9.03125 8.31907C8.6872 8.77066 8.51518 9.41696 8.51518 10.258Z" fill="#777777"/> +<path d="M20.735 11.9608L22.0129 6.82138H23.1495L21.2142 13.5455H20.2927L18.6769 8.44957L17.1041 13.5455H16.1825L14.2534 6.82138H15.3838L16.6925 11.8551L18.2407 6.82138H19.1561L20.735 11.9608Z" fill="#777777"/> +<path d="M27.0692 13.6697C26.1681 13.6697 25.4349 13.3714 24.8697 12.7749C24.3045 12.1741 24.0219 11.3725 24.0219 10.3699V10.1586C24.0219 9.49154 24.1468 8.89702 24.3967 8.375C24.6506 7.84884 25.0028 7.43868 25.4534 7.14453C25.908 6.84624 26.3995 6.69709 26.9279 6.69709C27.7921 6.69709 28.4638 6.98503 28.943 7.5609C29.4222 8.13678 29.6618 8.96123 29.6618 10.0343V10.5128H25.1585C25.1749 11.1757 25.3653 11.7122 25.7298 12.1223C26.0985 12.5283 26.5654 12.7314 27.1306 12.7314C27.532 12.7314 27.872 12.6485 28.1505 12.4828C28.429 12.3171 28.6727 12.0975 28.8816 11.824L29.5758 12.3709C29.0188 13.2368 28.1832 13.6697 27.0692 13.6697ZM26.9279 7.64169C26.4691 7.64169 26.0841 7.81155 25.7729 8.15128C25.4616 8.48686 25.2691 8.95916 25.1953 9.56818H28.5252V9.48118C28.4925 8.89702 28.3368 8.44543 28.0583 8.12642C27.7798 7.80327 27.403 7.64169 26.9279 7.64169Z" fill="#777777"/> +<path d="M34.1959 7.85298C34.0238 7.82398 33.8375 7.80948 33.6368 7.80948C32.8914 7.80948 32.3855 8.13056 32.1193 8.77273V13.5455H30.9827V6.82138H32.0886L32.107 7.59819C32.4797 6.99746 33.0081 6.69709 33.6921 6.69709C33.9133 6.69709 34.0812 6.72609 34.1959 6.78409V7.85298Z" fill="#777777"/> +<path d="M37.8821 13.6697C36.981 13.6697 36.2479 13.3714 35.6826 12.7749C35.1174 12.1741 34.8348 11.3725 34.8348 10.3699V10.1586C34.8348 9.49154 34.9597 8.89702 35.2096 8.375C35.4635 7.84884 35.8158 7.43868 36.2663 7.14453C36.7209 6.84624 37.2124 6.69709 37.7408 6.69709C38.605 6.69709 39.2767 6.98503 39.7559 7.5609C40.2351 8.13678 40.4747 8.96123 40.4747 10.0343V10.5128H35.9714C35.9878 11.1757 36.1782 11.7122 36.5428 12.1223C36.9114 12.5283 37.3783 12.7314 37.9435 12.7314C38.3449 12.7314 38.6849 12.6485 38.9634 12.4828C39.2419 12.3171 39.4856 12.0975 39.6945 11.824L40.3887 12.3709C39.8317 13.2368 38.9962 13.6697 37.8821 13.6697ZM37.7408 7.64169C37.2821 7.64169 36.8971 7.81155 36.5858 8.15128C36.2745 8.48686 36.082 8.95916 36.0083 9.56818H39.3382V9.48118C39.3054 8.89702 39.1497 8.44543 38.8712 8.12642C38.5927 7.80327 38.2159 7.64169 37.7408 7.64169Z" fill="#777777"/> +<path d="M41.5192 10.1275C41.5192 9.09588 41.7608 8.26728 42.2441 7.64169C42.7274 7.01196 43.3602 6.69709 44.1425 6.69709C44.9207 6.69709 45.5372 6.96638 45.9918 7.50497V4H47.1284V13.5455H46.0839L46.0287 12.8246C45.574 13.388 44.9412 13.6697 44.1302 13.6697C43.3602 13.6697 42.7315 13.3507 42.2441 12.7127C41.7608 12.0747 41.5192 11.242 41.5192 10.2145V10.1275ZM42.6558 10.258C42.6558 11.0203 42.8114 11.6169 43.1227 12.0478C43.434 12.4786 43.864 12.6941 44.4129 12.6941C45.1337 12.6941 45.66 12.3668 45.9918 11.7122V8.62358C45.6518 7.9897 45.1296 7.67276 44.4251 7.67276C43.8681 7.67276 43.434 7.89027 43.1227 8.32528C42.8114 8.7603 42.6558 9.40453 42.6558 10.258Z" fill="#777777"/> +<path d="M57.6464 10.258C57.6464 11.2855 57.4129 12.112 56.946 12.7376C56.4791 13.359 55.8524 13.6697 55.066 13.6697C54.2264 13.6697 53.5772 13.3694 53.1185 12.7686L53.0632 13.5455H52.0188V4H53.1553V7.5609C53.6141 6.98503 54.2469 6.69709 55.0538 6.69709C55.8606 6.69709 56.4934 7.00574 56.9522 7.62305C57.415 8.24035 57.6464 9.08552 57.6464 10.1586V10.258ZM56.5098 10.1275C56.5098 9.34446 56.3603 8.73958 56.0613 8.31285C55.7623 7.88613 55.3323 7.67276 54.7711 7.67276C54.0216 7.67276 53.483 8.02492 53.1553 8.72923V11.6376C53.5035 12.3419 54.0462 12.6941 54.7834 12.6941C55.3282 12.6941 55.7521 12.4807 56.0552 12.054C56.3583 11.6272 56.5098 10.9851 56.5098 10.1275Z" fill="#777777"/> +<path d="M61.1852 11.8613L62.7334 6.82138H63.9498L61.2773 14.5833C60.8637 15.7019 60.2063 16.2612 59.3052 16.2612L59.0902 16.2425L58.6663 16.1618V15.2296L58.9734 15.2544C59.3584 15.2544 59.6574 15.1757 59.8704 15.0183C60.0875 14.8609 60.2657 14.5729 60.4049 14.1545L60.6568 13.4709L58.2853 6.82138H59.5264L61.1852 11.8613Z" fill="#777777"/> +<path d="M72.7808 11.3763L74.178 9.93067L72.441 7.58664L70.2293 4.45722C69.448 5.79067 69 7.34287 69 9C69 11.7452 70.2293 14.203 72.1682 15.8537L74.9755 13.8697C74.0206 13.2748 73.2514 12.4087 72.7808 11.3763Z" fill="#777777"/> +<path d="M75.6237 3.78081L77.0693 5.17803L79.4134 3.44099L82.5428 1.22933C81.2093 0.447982 79.6571 0 78 0C75.2548 0 72.797 1.22933 71.1463 3.16816L73.1303 5.97552C73.7252 5.02063 74.5913 4.25139 75.6237 3.78081Z" fill="#777777"/> +<path d="M81.822 8.06933L83.559 10.4134L85.7707 13.5428C86.552 12.2093 87 10.6571 87 9C87 6.2548 85.7707 3.79695 83.8318 2.14628L81.0245 4.13031C81.9794 4.7252 82.7486 5.5913 83.2192 6.62368L81.822 8.06933Z" fill="#777777"/> +<path d="M84.8537 14.8318L82.8697 12.0245C82.2748 12.9794 81.4087 13.7486 80.3763 14.2192L78.9307 12.822L76.5866 14.559L73.4572 16.7707C74.7907 17.552 76.3429 18 78 18C80.7452 18 83.203 16.7707 84.8537 14.8318Z" fill="#777777"/> +<path d="M95.7487 3.89648C93.6141 3.89648 92.2419 5.54671 92.2419 8.8651C92.2419 12.1566 93.6231 13.8247 95.7487 13.8247C97.8742 13.8247 99.2733 12.1566 99.2733 8.8651C99.2733 5.57361 97.8742 3.89648 95.7487 3.89648ZM93.4348 8.87406C93.4348 6.41666 94.1971 4.97272 95.7397 4.97272C96.2061 4.97272 96.6097 5.10725 96.9594 5.38527L93.928 11.5826C93.6052 10.9279 93.4348 10.031 93.4348 8.87406ZM95.7487 12.7664C95.2823 12.7664 94.8877 12.6319 94.5469 12.3718L97.5783 6.18348C97.9101 6.85613 98.0715 7.74402 98.0715 8.87406C98.0715 11.3135 97.2913 12.7664 95.7487 12.7664Z" fill="#777777"/> +<path d="M104.96 10.085L106.915 6.7666H105.704L104.09 9.50203H103.596L102.018 6.7666H100.78L102.708 10.1298L100.565 13.6545H101.838L103.587 10.7666H104.072L105.785 13.6545H107.094L104.96 10.085Z" fill="#777777"/> +</svg> diff --git a/packages/instant/src/components/erc20_asset_amount_input.tsx b/packages/instant/src/components/erc20_asset_amount_input.tsx index 520ac33d5..b825255c4 100644 --- a/packages/instant/src/components/erc20_asset_amount_input.tsx +++ b/packages/instant/src/components/erc20_asset_amount_input.tsx @@ -22,7 +22,8 @@ export interface ERC20AssetAmountInputProps { onSelectAssetClick?: (asset?: ERC20Asset) => void; startingFontSizePx: number; fontColor?: ColorOption; - isDisabled: boolean; + isInputDisabled: boolean; + canSelectOtherAsset: boolean; numberOfAssetsAvailable?: number; } @@ -50,14 +51,15 @@ export class ERC20AssetAmountInput extends React.Component<ERC20AssetAmountInput ); } private readonly _renderContentForAsset = (asset: ERC20Asset): React.ReactNode => { - const { onChange, ...rest } = this.props; - const amountBorderBottom = this.props.isDisabled ? '' : `1px solid ${transparentWhite}`; + const { onChange, isInputDisabled, ...rest } = this.props; + const amountBorderBottom = isInputDisabled ? '' : `1px solid ${transparentWhite}`; const onSymbolClick = this._generateSelectAssetClickHandler(); return ( <React.Fragment> <Container borderBottom={amountBorderBottom} display="inline-block"> <ScalingAmountInput {...rest} + isDisabled={isInputDisabled} textLengthThreshold={this._textLengthThresholdForAsset(asset)} maxFontSizePx={this.props.startingFontSizePx} onAmountChange={this._handleChange} @@ -74,11 +76,11 @@ export class ERC20AssetAmountInput extends React.Component<ERC20AssetAmountInput fontSize={`${this.state.currentFontSizePx}px`} fontColor={ColorOption.white} textTransform="uppercase" - onClick={onSymbolClick} + onClick={this.props.canSelectOtherAsset ? onSymbolClick : undefined} > {assetUtils.formattedSymbolForAsset(asset)} </Text> - {this._renderChevronIcon()} + {this.props.canSelectOtherAsset && this._renderChevronIcon()} </Flex> </Container> </React.Fragment> diff --git a/packages/instant/src/components/zero_ex_instant_container.tsx b/packages/instant/src/components/zero_ex_instant_container.tsx index 698bfef17..47c938472 100644 --- a/packages/instant/src/components/zero_ex_instant_container.tsx +++ b/packages/instant/src/components/zero_ex_instant_container.tsx @@ -1,5 +1,7 @@ import * as React from 'react'; +import PoweredByLogo from '../assets/powered_by_0x.svg'; +import { ZERO_EX_SITE_URL } from '../constants'; import { AvailableERC20TokenSelector } from '../containers/available_erc20_token_selector'; import { ConnectedBuyOrderProgressOrPaymentMethod } from '../containers/connected_buy_order_progress_or_payment_method'; import { CurrentStandardSlidingPanel } from '../containers/current_standard_sliding_panel'; @@ -64,6 +66,17 @@ export class ZeroExInstantContainer extends React.Component<{}, ZeroExInstantCon </SlidingPanel> <CurrentStandardSlidingPanel /> </Container> + <Container + display={{ sm: 'none', default: 'block' }} + marginTop="10px" + marginLeft="auto" + marginRight="auto" + width="140px" + > + <a href={ZERO_EX_SITE_URL} target="_blank"> + <PoweredByLogo /> + </a> + </Container> </Container> </React.Fragment> ); diff --git a/packages/instant/src/components/zero_ex_instant_provider.tsx b/packages/instant/src/components/zero_ex_instant_provider.tsx index 8be53ee20..9814aabf8 100644 --- a/packages/instant/src/components/zero_ex_instant_provider.tsx +++ b/packages/instant/src/components/zero_ex_instant_provider.tsx @@ -12,6 +12,7 @@ import { DEFAULT_STATE, DefaultState, State } from '../redux/reducer'; import { store, Store } from '../redux/store'; import { fonts } from '../style/fonts'; import { AccountState, AffiliateInfo, AssetMetaData, Network, OrderSource } from '../types'; +import { analytics, disableAnalytics } from '../util/analytics'; import { assetUtils } from '../util/asset'; import { errorFlasher } from '../util/error_flasher'; import { gasPriceEstimator } from '../util/gas_price_estimator'; @@ -19,8 +20,6 @@ import { Heartbeater } from '../util/heartbeater'; import { generateAccountHeartbeater, generateBuyQuoteHeartbeater } from '../util/heartbeater_factory'; import { providerStateFactory } from '../util/provider_state_factory'; -fonts.include(); - export type ZeroExInstantProviderProps = ZeroExInstantProviderRequiredProps & Partial<ZeroExInstantProviderOptionalProps>; @@ -36,6 +35,7 @@ export interface ZeroExInstantProviderOptionalProps { additionalAssetMetaDataMap: ObjectMap<AssetMetaData>; networkId: Network; affiliateInfo: AffiliateInfo; + shouldDisableAnalyticsTracking: boolean; } export class ZeroExInstantProvider extends React.Component<ZeroExInstantProviderProps> { @@ -86,6 +86,7 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider } constructor(props: ZeroExInstantProviderProps) { super(props); + fonts.include(); const initialAppState = ZeroExInstantProvider._mergeDefaultStateWithProps(this.props); this._store = store.create(initialAppState); } @@ -121,6 +122,18 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider gasPriceEstimator.getGasInfoAsync(); // tslint:disable-next-line:no-floating-promises this._flashErrorIfWrongNetwork(); + + // Analytics + disableAnalytics(this.props.shouldDisableAnalyticsTracking || false); + analytics.addEventProperties({ + embeddedHost: window.location.host, + embeddedUrl: window.location.href, + networkId: state.network, + providerName: state.providerState.name, + gitSha: process.env.GIT_SHA, + npmVersion: process.env.NPM_PACKAGE_VERSION, + }); + analytics.trackInstantOpened(); } public componentWillUnmount(): void { if (this._accountUpdateHeartbeat) { diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts index 5bd2349b3..be6077ca9 100644 --- a/packages/instant/src/constants.ts +++ b/packages/instant/src/constants.ts @@ -16,6 +16,7 @@ export const BUY_QUOTE_UPDATE_INTERVAL_TIME_MS = ONE_SECOND_MS * 15; export const DEFAULT_GAS_PRICE = GWEI_IN_WEI.mul(6); export const DEFAULT_ESTIMATED_TRANSACTION_TIME_MS = ONE_MINUTE_MS * 2; export const ETH_GAS_STATION_API_BASE_URL = 'https://ethgasstation.info'; +export const HEAP_ANALYTICS_ID = process.env.HEAP_ANALYTICS_ID; export const COINBASE_API_BASE_URL = 'https://api.coinbase.com/v2'; export const PROGRESS_STALL_AT_WIDTH = '95%'; export const PROGRESS_FINISH_ANIMATION_TIME_MS = 200; @@ -31,6 +32,7 @@ export const ETHEREUM_NODE_URL_BY_NETWORK = { [Network.Mainnet]: 'https://mainnet.infura.io/', [Network.Kovan]: 'https://kovan.infura.io/', }; +export const ZERO_EX_SITE_URL = 'https://www.0xproject.com/'; export const BLOCK_POLLING_INTERVAL_MS = 10000; // 10s export const NO_ACCOUNT: AccountNotReady = { state: AccountState.None, @@ -47,4 +49,5 @@ export const PROVIDER_TYPE_TO_NAME: { [key in ProviderType]: string } = { [ProviderType.Mist]: 'Mist', [ProviderType.CoinbaseWallet]: 'Coinbase Wallet', [ProviderType.Parity]: 'Parity', + [ProviderType.Fallback]: 'Fallback', }; diff --git a/packages/instant/src/containers/selected_erc20_asset_amount_input.ts b/packages/instant/src/containers/selected_erc20_asset_amount_input.ts index 2c2661e1a..a39bc46a2 100644 --- a/packages/instant/src/containers/selected_erc20_asset_amount_input.ts +++ b/packages/instant/src/containers/selected_erc20_asset_amount_input.ts @@ -23,9 +23,10 @@ interface ConnectedState { assetBuyer: AssetBuyer; value?: BigNumber; asset?: ERC20Asset; - isDisabled: boolean; + isInputDisabled: boolean; numberOfAssetsAvailable?: number; affiliateInfo?: AffiliateInfo; + canSelectOtherAsset: boolean; } interface ConnectedDispatch { @@ -43,21 +44,27 @@ type FinalProps = ConnectedProps & SelectedERC20AssetAmountInputProps; const mapStateToProps = (state: State, _ownProps: SelectedERC20AssetAmountInputProps): ConnectedState => { const processState = state.buyOrderState.processState; - const isEnabled = processState === OrderProcessState.None || processState === OrderProcessState.Failure; - const isDisabled = !isEnabled; + const isInputEnabled = processState === OrderProcessState.None || processState === OrderProcessState.Failure; + const isInputDisabled = !isInputEnabled; const selectedAsset = !_.isUndefined(state.selectedAsset) && state.selectedAsset.metaData.assetProxyId === AssetProxyId.ERC20 ? (state.selectedAsset as ERC20Asset) : undefined; const numberOfAssetsAvailable = _.isUndefined(state.availableAssets) ? undefined : state.availableAssets.length; + const canSelectOtherAsset = + numberOfAssetsAvailable && numberOfAssetsAvailable > 1 + ? isInputEnabled || processState === OrderProcessState.Success + : false; + const assetBuyer = state.providerState.assetBuyer; return { assetBuyer, value: state.selectedAssetUnitAmount, asset: selectedAsset, - isDisabled, + isInputDisabled, numberOfAssetsAvailable, affiliateInfo: state.affiliateInfo, + canSelectOtherAsset, }; }; @@ -102,8 +109,9 @@ const mergeProps = ( onChange: (value, asset) => { connectedDispatch.updateBuyQuote(connectedState.assetBuyer, value, asset, connectedState.affiliateInfo); }, - isDisabled: connectedState.isDisabled, + isInputDisabled: connectedState.isInputDisabled, numberOfAssetsAvailable: connectedState.numberOfAssetsAvailable, + canSelectOtherAsset: connectedState.canSelectOtherAsset, }; }; diff --git a/packages/instant/src/globals.d.ts b/packages/instant/src/globals.d.ts index 94e63a32d..1b5fa443d 100644 --- a/packages/instant/src/globals.d.ts +++ b/packages/instant/src/globals.d.ts @@ -1,3 +1,9 @@ +declare module '*.svg' { + const content: any; + /* tslint:disable */ + export default content; + /* tslint:enable */ +} declare module '*.json' { const json: any; /* tslint:disable */ diff --git a/packages/instant/src/index.umd.ts b/packages/instant/src/index.umd.ts index 64f888e00..59f658b33 100644 --- a/packages/instant/src/index.umd.ts +++ b/packages/instant/src/index.umd.ts @@ -43,6 +43,9 @@ export const render = (config: ZeroExInstantConfig, selector: string = DEFAULT_Z if (!_.isUndefined(config.shouldDisablePushToHistory)) { assert.isBoolean('shouldDisablePushToHistory', config.shouldDisablePushToHistory); } + if (!_.isUndefined(props.shouldDisableAnalyticsTracking)) { + assert.isBoolean('props.shouldDisableAnalyticsTracking', props.shouldDisableAnalyticsTracking); + } assert.isString('selector', selector); // Render instant and return a callback that allows you to remove it from the DOM. const renderInstant = () => { diff --git a/packages/instant/src/redux/analytics_middleware.ts b/packages/instant/src/redux/analytics_middleware.ts new file mode 100644 index 000000000..f971dbd33 --- /dev/null +++ b/packages/instant/src/redux/analytics_middleware.ts @@ -0,0 +1,59 @@ +import { Web3Wrapper } from '@0x/web3-wrapper'; +import * as _ from 'lodash'; +import { Middleware } from 'redux'; + +import { ETH_DECIMALS } from '../constants'; +import { Account, AccountState } from '../types'; +import { analytics } from '../util/analytics'; + +import { Action, ActionTypes } from './actions'; + +import { State } from './reducer'; + +const shouldTriggerWalletReady = (prevAccount: Account, curAccount: Account): boolean => { + const didJustTurnReady = curAccount.state === AccountState.Ready && prevAccount.state !== AccountState.Ready; + if (didJustTurnReady) { + return true; + } + + if (curAccount.state === AccountState.Ready && prevAccount.state === AccountState.Ready) { + // Account was ready, and is now ready again, but address has changed + return curAccount.address !== prevAccount.address; + } + + return false; +}; + +export const analyticsMiddleware: Middleware = store => next => middlewareAction => { + const prevState = store.getState() as State; + const prevAccount = prevState.providerState.account; + + const nextAction = next(middlewareAction) as Action; + + const curState = store.getState() as State; + const curAccount = curState.providerState.account; + + switch (nextAction.type) { + case ActionTypes.SET_ACCOUNT_STATE_READY: + if (curAccount.state === AccountState.Ready && shouldTriggerWalletReady(prevAccount, curAccount)) { + const ethAddress = curAccount.address; + analytics.addUserProperties({ ethAddress }); + analytics.trackWalletReady(); + } + break; + case ActionTypes.UPDATE_ACCOUNT_ETH_BALANCE: + if ( + curAccount.state === AccountState.Ready && + curAccount.ethBalanceInWei && + !_.isEqual(curAccount, prevAccount) + ) { + const ethBalanceInUnitAmount = Web3Wrapper.toUnitAmount( + curAccount.ethBalanceInWei, + ETH_DECIMALS, + ).toString(); + analytics.addUserProperties({ ethBalanceInUnitAmount }); + } + } + + return nextAction; +}; diff --git a/packages/instant/src/redux/store.ts b/packages/instant/src/redux/store.ts index 20710765d..11bba3876 100644 --- a/packages/instant/src/redux/store.ts +++ b/packages/instant/src/redux/store.ts @@ -1,7 +1,8 @@ import * as _ from 'lodash'; -import { createStore, Store as ReduxStore } from 'redux'; -import { devToolsEnhancer } from 'redux-devtools-extension/developmentOnly'; +import { applyMiddleware, createStore, Store as ReduxStore } from 'redux'; +import { composeWithDevTools } from 'redux-devtools-extension/developmentOnly'; +import { analyticsMiddleware } from './analytics_middleware'; import { createReducer, State } from './reducer'; export type Store = ReduxStore<State>; @@ -9,6 +10,6 @@ export type Store = ReduxStore<State>; export const store = { create: (initialState: State): Store => { const reducer = createReducer(initialState); - return createStore(reducer, initialState, devToolsEnhancer({})); + return createStore(reducer, initialState, composeWithDevTools(applyMiddleware(analyticsMiddleware))); }, }; diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts index 2d4a8a850..999d50fed 100644 --- a/packages/instant/src/types.ts +++ b/packages/instant/src/types.ts @@ -165,4 +165,5 @@ export enum ProviderType { Mist = 'MIST', CoinbaseWallet = 'COINBASE_WALLET', Cipher = 'CIPHER', + Fallback = 'FALLBACK', } diff --git a/packages/instant/src/util/analytics.ts b/packages/instant/src/util/analytics.ts new file mode 100644 index 000000000..2ffaac1dd --- /dev/null +++ b/packages/instant/src/util/analytics.ts @@ -0,0 +1,64 @@ +import { ObjectMap } from '@0x/types'; + +import { heapUtil } from './heap'; + +let isDisabled = false; +export const disableAnalytics = (shouldDisableAnalytics: boolean) => { + isDisabled = shouldDisableAnalytics; +}; +export const evaluateIfEnabled = (fnCall: () => void) => { + if (isDisabled) { + return; + } + fnCall(); +}; + +enum EventNames { + INSTANT_OPENED = 'Instant - Opened', + WALLET_READY = 'Wallet - Ready', +} +const track = (eventName: EventNames, eventData: ObjectMap<string | number> = {}): void => { + evaluateIfEnabled(() => { + heapUtil.evaluateHeapCall(heap => heap.track(eventName, eventData)); + }); +}; +function trackingEventFnWithoutPayload(eventName: EventNames): () => void { + return () => { + track(eventName); + }; +} +// tslint:disable-next-line:no-unused-variable +function trackingEventFnWithPayload<T extends ObjectMap<string | number>>( + eventName: EventNames, +): (eventDataProperties: T) => void { + return (eventDataProperties: T) => { + track(eventName, eventDataProperties); + }; +} + +export interface AnalyticsUserOptions { + ethAddress?: string; + ethBalanceInUnitAmount?: string; +} +export interface AnalyticsEventOptions { + embeddedHost?: string; + embeddedUrl?: string; + networkId?: number; + providerName?: string; + gitSha?: string; + npmVersion?: string; +} +export const analytics = { + addUserProperties: (properties: AnalyticsUserOptions): void => { + evaluateIfEnabled(() => { + heapUtil.evaluateHeapCall(heap => heap.addUserProperties(properties)); + }); + }, + addEventProperties: (properties: AnalyticsEventOptions): void => { + evaluateIfEnabled(() => { + heapUtil.evaluateHeapCall(heap => heap.addEventProperties(properties)); + }); + }, + trackWalletReady: trackingEventFnWithoutPayload(EventNames.WALLET_READY), + trackInstantOpened: trackingEventFnWithoutPayload(EventNames.INSTANT_OPENED), +}; diff --git a/packages/instant/src/util/heap.ts b/packages/instant/src/util/heap.ts new file mode 100644 index 000000000..78ec3b3cc --- /dev/null +++ b/packages/instant/src/util/heap.ts @@ -0,0 +1,113 @@ +import { ObjectMap } from '@0x/types'; +import { logUtils } from '@0x/utils'; +import * as _ from 'lodash'; + +import { HEAP_ANALYTICS_ID } from '../constants'; + +import { AnalyticsEventOptions, AnalyticsUserOptions } from './analytics'; + +export interface HeapAnalytics { + loaded: boolean; + appid: string; + identify(id: string, idType: string): void; + track(eventName: string, eventProperties?: ObjectMap<string | number>): void; + resetIdentity(): void; + addUserProperties(properties: AnalyticsUserOptions): void; + addEventProperties(properties: AnalyticsEventOptions): void; + removeEventProperty(property: string): void; + clearEventProperties(): void; +} +interface ModifiedWindow { + heap?: HeapAnalytics; + zeroExInstantLoadedHeap?: boolean; +} +const getWindow = (): ModifiedWindow => { + return window as ModifiedWindow; +}; + +const setupZeroExInstantHeap = () => { + if (_.isUndefined(HEAP_ANALYTICS_ID)) { + return; + } + + const curWindow = getWindow(); + // Set property to specify that this is zeroEx's heap + curWindow.zeroExInstantLoadedHeap = true; + + // Typescript-compatible version of https://docs.heapanalytics.com/docs/installation + /* tslint:disable */ + ((window as any).heap = (window as any).heap || []), + ((window as any).heap.load = function(e: any, t: any) { + ((window as any).heap.appid = e), ((window as any).heap.config = t = t || {}); + var r = t.forceSSL || 'https:' === (document.location as Location).protocol, + a = document.createElement('script'); + (a.type = 'text/javascript'), + (a.async = !0), + (a.src = (r ? 'https:' : 'http:') + '//cdn.heapanalytics.com/js/heap-' + e + '.js'); + var n = document.getElementsByTagName('script')[0]; + (n.parentNode as Node).insertBefore(a, n); + for ( + var o = function(e: any) { + return function() { + (window as any).heap.push([e].concat(Array.prototype.slice.call(arguments, 0))); + }; + }, + p = [ + 'addEventProperties', + 'addUserProperties', + 'clearEventProperties', + 'identify', + 'resetIdentity', + 'removeEventProperty', + 'setEventProperties', + 'track', + 'unsetEventProperty', + ], + c = 0; + c < p.length; + c++ + ) + (window as any).heap[p[c]] = o(p[c]); + }); + (window as any).heap.load(HEAP_ANALYTICS_ID); + /* tslint:enable */ + + return curWindow.heap as HeapAnalytics; +}; + +export const heapUtil = { + getHeap: (): HeapAnalytics | undefined => { + const curWindow = getWindow(); + const hasOtherExistingHeapIntegration = curWindow.heap && !curWindow.zeroExInstantLoadedHeap; + if (hasOtherExistingHeapIntegration) { + return undefined; + } + + const zeroExInstantHeapIntegration = curWindow.zeroExInstantLoadedHeap && curWindow.heap; + if (zeroExInstantHeapIntegration) { + return zeroExInstantHeapIntegration; + } + + return setupZeroExInstantHeap(); + }, + evaluateHeapCall: (heapFunctionCall: (heap: HeapAnalytics) => void): void => { + if (_.isUndefined(HEAP_ANALYTICS_ID)) { + return; + } + + const curHeap = heapUtil.getHeap(); + if (curHeap) { + try { + if (curHeap.appid !== HEAP_ANALYTICS_ID) { + // Integrator has included heap after us and reset the app id + return; + } + heapFunctionCall(curHeap); + } catch (e) { + // We never want analytics to crash our React component + // TODO(sk): error reporter here + logUtils.log('Analytics error', e); + } + } + }, +}; diff --git a/packages/instant/src/util/provider_state_factory.ts b/packages/instant/src/util/provider_state_factory.ts index 452a71460..7c788dff2 100644 --- a/packages/instant/src/util/provider_state_factory.ts +++ b/packages/instant/src/util/provider_state_factory.ts @@ -56,7 +56,7 @@ export const providerStateFactory = { getInitialProviderStateFallback: (orderSource: OrderSource, network: Network): ProviderState => { const provider = providerFactory.getFallbackNoSigningProvider(network); const providerState: ProviderState = { - name: envUtil.getProviderName(provider), + name: 'Fallback', provider, web3Wrapper: new Web3Wrapper(provider), assetBuyer: assetBuyerFactory.getAssetBuyer(provider, orderSource, network), diff --git a/packages/instant/tsconfig.json b/packages/instant/tsconfig.json index 28a6190b8..14b0ad8f7 100644 --- a/packages/instant/tsconfig.json +++ b/packages/instant/tsconfig.json @@ -2,16 +2,11 @@ "extends": "../../tsconfig", "compilerOptions": { "outDir": "lib", - "rootDir": ".", + "rootDir": "src", "jsx": "react", - "allowSyntheticDefaultImports": true, "noImplicitAny": true, - "module": "ESNext", - "moduleResolution": "node", - "lib": ["es2015", "dom"], - "target": "es5", - "sourceMap": true + "allowSyntheticDefaultImports": true }, - "include": ["./src/**/*", "./test/**/*"], + "include": ["./src/**/*"], "exclude": ["./src/index.umd.ts"] } diff --git a/packages/instant/webpack.config.js b/packages/instant/webpack.config.js index fae303712..41276809c 100644 --- a/packages/instant/webpack.config.js +++ b/packages/instant/webpack.config.js @@ -1,46 +1,81 @@ -const path = require('path'); +const childProcess = require('child_process'); const ip = require('ip'); +const path = require('path'); +const webpack = require('webpack'); + // The common js bundle (not this one) is built using tsc. // The umd bundle (this one) has a different entrypoint. -const outputPath = process.env.WEBPACK_OUTPUT_PATH || 'umd'; -const config = { - entry: { - instant: './src/index.umd.ts', - }, - output: { - filename: '[name].js', - path: path.resolve(__dirname, outputPath), - library: 'zeroExInstant', - libraryTarget: 'umd', - }, - devtool: 'source-map', - resolve: { - extensions: ['.js', '.json', '.ts', '.tsx'], - }, - module: { - rules: [ - { - test: /\.(ts|tsx)$/, - loader: 'awesome-typescript-loader', - }, - { - test: /\.svg$/, - loader: 'svg-react-loader', - }, + +const GIT_SHA = childProcess + .execSync('git rev-parse HEAD') + .toString() + .trim(); + +const HEAP_PRODUCTION_ENV_VAR_NAME = 'INSTANT_HEAP_ANALYTICS_ID_PRODUCTION'; +const HEAP_DEVELOPMENT_ENV_VAR_NAME = 'INSTANT_HEAP_ANALYTICS_ID_DEVELOPMENT'; +const getHeapAnalyticsId = modeName => { + if (modeName === 'production') { + return process.env[HEAP_PRODUCTION_ENV_VAR_NAME]; + } + + if (modeName === 'development') { + return process.env[HEAP_DEVELOPMENT_ENV_VAR_NAME]; + } + + return undefined; +}; + +module.exports = (env, argv) => { + const outputPath = process.env.WEBPACK_OUTPUT_PATH || 'umd'; + const config = { + entry: { + instant: './src/index.umd.ts', + }, + output: { + filename: '[name].js', + path: path.resolve(__dirname, outputPath), + library: 'zeroExInstant', + libraryTarget: 'umd', + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env': { + GIT_SHA: JSON.stringify(GIT_SHA), + HEAP_ANALYTICS_ID: getHeapAnalyticsId(argv.mode), + NPM_PACKAGE_VERSION: JSON.stringify(process.env.npm_package_version), + }, + }), ], - }, - devServer: { - contentBase: path.join(__dirname, 'public'), - port: 5000, - host: '0.0.0.0', - after: () => { - if (config.devServer.host === '0.0.0.0') { - console.log( - `webpack-dev-server can be accessed externally at: http://${ip.address()}:${config.devServer.port}`, - ); - } + devtool: 'source-map', + resolve: { + extensions: ['.js', '.json', '.ts', '.tsx'], }, - }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + loader: 'awesome-typescript-loader', + }, + { + test: /\.svg$/, + loader: 'svg-react-loader', + }, + ], + }, + devServer: { + contentBase: path.join(__dirname, 'public'), + port: 5000, + host: '0.0.0.0', + after: () => { + if (config.devServer.host === '0.0.0.0') { + console.log( + `webpack-dev-server can be accessed externally at: http://${ip.address()}:${ + config.devServer.port + }`, + ); + } + }, + }, + }; + return config; }; - -module.exports = config; |