aboutsummaryrefslogtreecommitdiffstats
path: root/packages/instant/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/instant/src')
-rw-r--r--packages/instant/src/containers/connected_account_payment_method.ts8
-rw-r--r--packages/instant/src/index.umd.ts111
-rw-r--r--packages/instant/src/redux/analytics_middleware.ts34
-rw-r--r--packages/instant/src/redux/async_data.ts2
-rw-r--r--packages/instant/src/util/analytics.ts32
-rw-r--r--packages/instant/src/util/heap.ts4
6 files changed, 129 insertions, 62 deletions
diff --git a/packages/instant/src/containers/connected_account_payment_method.ts b/packages/instant/src/containers/connected_account_payment_method.ts
index eacbadfca..cdeb49a25 100644
--- a/packages/instant/src/containers/connected_account_payment_method.ts
+++ b/packages/instant/src/containers/connected_account_payment_method.ts
@@ -12,6 +12,7 @@ import { Action, actions } from '../redux/actions';
import { asyncData } from '../redux/async_data';
import { State } from '../redux/reducer';
import { Network, Omit, OperatingSystem, ProviderState, StandardSlidingPanelContent } from '../types';
+import { analytics } from '../util/analytics';
import { envUtil } from '../util/env';
export interface ConnectedAccountPaymentMethodProps {}
@@ -40,8 +41,11 @@ const mapDispatchToProps = (
ownProps: ConnectedAccountPaymentMethodProps,
): ConnectedDispatch => ({
openInstallWalletPanel: () => dispatch(actions.openStandardSlidingPanel(StandardSlidingPanelContent.InstallWallet)),
- unlockWalletAndDispatchToStore: async (providerState: ProviderState) =>
- asyncData.fetchAccountInfoAndDispatchToStore(providerState, dispatch, true),
+ unlockWalletAndDispatchToStore: (providerState: ProviderState) => {
+ analytics.trackAccountUnlockRequested();
+ // tslint:disable-next-line:no-floating-promises
+ asyncData.fetchAccountInfoAndDispatchToStore(providerState, dispatch, true);
+ },
});
const mergeProps = (
diff --git a/packages/instant/src/index.umd.ts b/packages/instant/src/index.umd.ts
index 5010347b3..3a8694d6a 100644
--- a/packages/instant/src/index.umd.ts
+++ b/packages/instant/src/index.umd.ts
@@ -5,40 +5,50 @@ import * as ReactDOM from 'react-dom';
import { DEFAULT_ZERO_EX_CONTAINER_SELECTOR, INJECTED_DIV_CLASS, INJECTED_DIV_ID } from './constants';
import { ZeroExInstantOverlay, ZeroExInstantOverlayProps } from './index';
import { assert } from './util/assert';
+import { util } from './util/util';
-export const render = (props: ZeroExInstantOverlayProps, selector: string = DEFAULT_ZERO_EX_CONTAINER_SELECTOR) => {
- assert.isValidOrderSource('orderSource', props.orderSource);
- if (!_.isUndefined(props.defaultSelectedAssetData)) {
- assert.isHexString('defaultSelectedAssetData', props.defaultSelectedAssetData);
+const isInstantRendered = (): boolean => !!document.getElementById(INJECTED_DIV_ID);
+
+const validateInstantRenderConfig = (config: ZeroExInstantConfig, selector: string) => {
+ assert.isValidOrderSource('orderSource', config.orderSource);
+ if (!_.isUndefined(config.defaultSelectedAssetData)) {
+ assert.isHexString('defaultSelectedAssetData', config.defaultSelectedAssetData);
+ }
+ if (!_.isUndefined(config.additionalAssetMetaDataMap)) {
+ assert.isValidAssetMetaDataMap('additionalAssetMetaDataMap', config.additionalAssetMetaDataMap);
}
- if (!_.isUndefined(props.additionalAssetMetaDataMap)) {
- assert.isValidAssetMetaDataMap('props.additionalAssetMetaDataMap', props.additionalAssetMetaDataMap);
+ if (!_.isUndefined(config.defaultAssetBuyAmount)) {
+ assert.isNumber('defaultAssetBuyAmount', config.defaultAssetBuyAmount);
}
- if (!_.isUndefined(props.defaultAssetBuyAmount)) {
- assert.isNumber('props.defaultAssetBuyAmount', props.defaultAssetBuyAmount);
+ if (!_.isUndefined(config.networkId)) {
+ assert.isNumber('networkId', config.networkId);
}
- if (!_.isUndefined(props.networkId)) {
- assert.isNumber('props.networkId', props.networkId);
+ if (!_.isUndefined(config.availableAssetDatas)) {
+ assert.areValidAssetDatas('availableAssetDatas', config.availableAssetDatas);
}
- if (!_.isUndefined(props.availableAssetDatas)) {
- assert.areValidAssetDatas('availableAssetDatas', props.availableAssetDatas);
+ if (!_.isUndefined(config.onClose)) {
+ assert.isFunction('onClose', config.onClose);
}
- if (!_.isUndefined(props.onClose)) {
- assert.isFunction('props.onClose', props.onClose);
+ if (!_.isUndefined(config.zIndex)) {
+ assert.isNumber('zIndex', config.zIndex);
}
- if (!_.isUndefined(props.zIndex)) {
- assert.isNumber('props.zIndex', props.zIndex);
+ if (!_.isUndefined(config.affiliateInfo)) {
+ assert.isValidAffiliateInfo('affiliateInfo', config.affiliateInfo);
}
- if (!_.isUndefined(props.affiliateInfo)) {
- assert.isValidAffiliateInfo('props.affiliateInfo', props.affiliateInfo);
+ if (!_.isUndefined(config.provider)) {
+ assert.isWeb3Provider('provider', config.provider);
}
- if (!_.isUndefined(props.provider)) {
- assert.isWeb3Provider('props.provider', props.provider);
+ if (!_.isUndefined(config.shouldDisablePushToHistory)) {
+ assert.isBoolean('shouldDisablePushToHistory', config.shouldDisablePushToHistory);
}
- if (!_.isUndefined(props.shouldDisableAnalyticsTracking)) {
- assert.isBoolean('props.shouldDisableAnalyticsTracking', props.shouldDisableAnalyticsTracking);
+ if (!_.isUndefined(config.shouldDisableAnalyticsTracking)) {
+ assert.isBoolean('shouldDisableAnalyticsTracking', config.shouldDisableAnalyticsTracking);
}
assert.isString('selector', selector);
+};
+
+// Render instant and return a callback that allows you to remove it from the DOM.
+const renderInstant = (config: ZeroExInstantConfig, selector: string) => {
const appendToIfExists = document.querySelector(selector);
assert.assert(!_.isNull(appendToIfExists), `Could not find div with selector: ${selector}`);
const appendTo = appendToIfExists as Element;
@@ -46,14 +56,57 @@ export const render = (props: ZeroExInstantOverlayProps, selector: string = DEFA
injectedDiv.setAttribute('id', INJECTED_DIV_ID);
injectedDiv.setAttribute('class', INJECTED_DIV_CLASS);
appendTo.appendChild(injectedDiv);
+ const closeInstant = () => {
+ if (!_.isUndefined(config.onClose)) {
+ config.onClose();
+ }
+ appendTo.removeChild(injectedDiv);
+ };
const instantOverlayProps = {
- ...props,
- onClose: () => {
- appendTo.removeChild(injectedDiv);
- if (!_.isUndefined(props.onClose)) {
- props.onClose();
- }
- },
+ ...config,
+ // If we are using the history API, just go back to close
+ onClose: () => (config.shouldDisablePushToHistory ? closeInstant() : window.history.back()),
};
ReactDOM.render(React.createElement(ZeroExInstantOverlay, instantOverlayProps), injectedDiv);
+ return closeInstant;
+};
+
+export interface ZeroExInstantConfig extends ZeroExInstantOverlayProps {
+ shouldDisablePushToHistory?: boolean;
+}
+
+export const render = (config: ZeroExInstantConfig, selector: string = DEFAULT_ZERO_EX_CONTAINER_SELECTOR) => {
+ validateInstantRenderConfig(config, selector);
+ if (config.shouldDisablePushToHistory) {
+ if (!isInstantRendered()) {
+ renderInstant(config, selector);
+ }
+ return;
+ }
+ // Before we render, push to history saying that instant is showing for this part of the history.
+ window.history.pushState({ zeroExInstantShowing: true }, '0x Instant');
+ let removeInstant = renderInstant(config, selector);
+ // If the integrator defined a popstate handler, save it to __zeroExInstantIntegratorsPopStateHandler
+ // unless we have already done so on a previous render.
+ const anyWindow = window as any;
+ if (window.onpopstate && !anyWindow.__zeroExInstantIntegratorsPopStateHandler) {
+ anyWindow.__zeroExInstantIntegratorsPopStateHandler = window.onpopstate.bind(window);
+ }
+ const integratorsOnPopStateHandler = anyWindow.__zeroExInstantIntegratorsPopStateHandler || util.boundNoop;
+ const onPopStateHandler = (e: PopStateEvent) => {
+ integratorsOnPopStateHandler(e);
+ const newState = e.state;
+ if (newState && newState.zeroExInstantShowing) {
+ // We have returned to a history state that expects instant to be rendered.
+ if (!isInstantRendered()) {
+ removeInstant = renderInstant(config, selector);
+ }
+ } else {
+ // History has changed to a different state.
+ if (isInstantRendered()) {
+ removeInstant();
+ }
+ }
+ };
+ window.onpopstate = onPopStateHandler;
};
diff --git a/packages/instant/src/redux/analytics_middleware.ts b/packages/instant/src/redux/analytics_middleware.ts
index f971dbd33..299c2560e 100644
--- a/packages/instant/src/redux/analytics_middleware.ts
+++ b/packages/instant/src/redux/analytics_middleware.ts
@@ -10,20 +10,6 @@ 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;
@@ -35,10 +21,24 @@ export const analyticsMiddleware: Middleware = store => next => middlewareAction
switch (nextAction.type) {
case ActionTypes.SET_ACCOUNT_STATE_READY:
- if (curAccount.state === AccountState.Ready && shouldTriggerWalletReady(prevAccount, curAccount)) {
+ if (curAccount.state === AccountState.Ready) {
+ const didJustTurnReady = prevAccount.state !== AccountState.Ready;
+ const didJustUpdateAddress =
+ prevAccount.state === AccountState.Ready && prevAccount.address !== curAccount.address;
const ethAddress = curAccount.address;
- analytics.addUserProperties({ ethAddress });
- analytics.trackWalletReady();
+ if (didJustTurnReady) {
+ analytics.trackAccountReady(ethAddress);
+ analytics.addUserProperties({ lastKnownEthAddress: ethAddress });
+ } else if (didJustUpdateAddress) {
+ analytics.trackAccountAddressChanged(ethAddress);
+ analytics.addUserProperties({ lastKnownEthAddress: ethAddress });
+ }
+ }
+ break;
+ case ActionTypes.SET_ACCOUNT_STATE_LOCKED:
+ if (prevAccount.state !== AccountState.Locked && curAccount.state === AccountState.Locked) {
+ // if we are moving from account not locked to account locked, track `Account - Locked`
+ analytics.trackAccountLocked();
}
break;
case ActionTypes.UPDATE_ACCOUNT_ETH_BALANCE:
diff --git a/packages/instant/src/redux/async_data.ts b/packages/instant/src/redux/async_data.ts
index 5d30388b8..6feb760e7 100644
--- a/packages/instant/src/redux/async_data.ts
+++ b/packages/instant/src/redux/async_data.ts
@@ -5,6 +5,7 @@ import { Dispatch } from 'redux';
import { BIG_NUMBER_ZERO } from '../constants';
import { AccountState, ERC20Asset, OrderProcessState, ProviderState } from '../types';
+import { analytics } from '../util/analytics';
import { assetUtils } from '../util/asset';
import { buyQuoteUpdater } from '../util/buy_quote_updater';
import { coinbaseApi } from '../util/coinbase_api';
@@ -58,6 +59,7 @@ export const asyncData = {
? await (provider as any).enable()
: await web3Wrapper.getAvailableAddressesAsync();
} catch (e) {
+ analytics.trackAccountUnlockDenied();
dispatch(actions.setAccountStateLocked());
return;
}
diff --git a/packages/instant/src/util/analytics.ts b/packages/instant/src/util/analytics.ts
index 2ffaac1dd..e389e1530 100644
--- a/packages/instant/src/util/analytics.ts
+++ b/packages/instant/src/util/analytics.ts
@@ -1,6 +1,4 @@
-import { ObjectMap } from '@0x/types';
-
-import { heapUtil } from './heap';
+import { EventProperties, heapUtil } from './heap';
let isDisabled = false;
export const disableAnalytics = (shouldDisableAnalytics: boolean) => {
@@ -15,11 +13,15 @@ export const evaluateIfEnabled = (fnCall: () => void) => {
enum EventNames {
INSTANT_OPENED = 'Instant - Opened',
- WALLET_READY = 'Wallet - Ready',
+ ACCOUNT_LOCKED = 'Account - Locked',
+ ACCOUNT_READY = 'Account - Ready',
+ ACCOUNT_UNLOCK_REQUESTED = 'Account - Unlock Requested',
+ ACCOUNT_UNLOCK_DENIED = 'Account - Unlock Denied',
+ ACCOUNT_ADDRESS_CHANGED = 'Account - Address Changed',
}
-const track = (eventName: EventNames, eventData: ObjectMap<string | number> = {}): void => {
+const track = (eventName: EventNames, eventProperties: EventProperties = {}): void => {
evaluateIfEnabled(() => {
- heapUtil.evaluateHeapCall(heap => heap.track(eventName, eventData));
+ heapUtil.evaluateHeapCall(heap => heap.track(eventName, eventProperties));
});
};
function trackingEventFnWithoutPayload(eventName: EventNames): () => void {
@@ -28,16 +30,14 @@ function trackingEventFnWithoutPayload(eventName: EventNames): () => void {
};
}
// 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);
+function trackingEventFnWithPayload(eventName: EventNames): (eventProperties: EventProperties) => void {
+ return (eventProperties: EventProperties) => {
+ track(eventName, eventProperties);
};
}
export interface AnalyticsUserOptions {
- ethAddress?: string;
+ lastKnownEthAddress?: string;
ethBalanceInUnitAmount?: string;
}
export interface AnalyticsEventOptions {
@@ -48,6 +48,7 @@ export interface AnalyticsEventOptions {
gitSha?: string;
npmVersion?: string;
}
+
export const analytics = {
addUserProperties: (properties: AnalyticsUserOptions): void => {
evaluateIfEnabled(() => {
@@ -59,6 +60,11 @@ export const analytics = {
heapUtil.evaluateHeapCall(heap => heap.addEventProperties(properties));
});
},
- trackWalletReady: trackingEventFnWithoutPayload(EventNames.WALLET_READY),
trackInstantOpened: trackingEventFnWithoutPayload(EventNames.INSTANT_OPENED),
+ trackAccountLocked: trackingEventFnWithoutPayload(EventNames.ACCOUNT_LOCKED),
+ trackAccountReady: (address: string) => trackingEventFnWithPayload(EventNames.ACCOUNT_READY)({ address }),
+ trackAccountUnlockRequested: trackingEventFnWithoutPayload(EventNames.ACCOUNT_UNLOCK_REQUESTED),
+ trackAccountUnlockDenied: trackingEventFnWithoutPayload(EventNames.ACCOUNT_UNLOCK_DENIED),
+ trackAccountAddressChanged: (address: string) =>
+ trackingEventFnWithPayload(EventNames.ACCOUNT_ADDRESS_CHANGED)({ address }),
};
diff --git a/packages/instant/src/util/heap.ts b/packages/instant/src/util/heap.ts
index 78ec3b3cc..7c53c9918 100644
--- a/packages/instant/src/util/heap.ts
+++ b/packages/instant/src/util/heap.ts
@@ -6,11 +6,13 @@ import { HEAP_ANALYTICS_ID } from '../constants';
import { AnalyticsEventOptions, AnalyticsUserOptions } from './analytics';
+export type EventProperties = ObjectMap<string | number>;
+
export interface HeapAnalytics {
loaded: boolean;
appid: string;
identify(id: string, idType: string): void;
- track(eventName: string, eventProperties?: ObjectMap<string | number>): void;
+ track(eventName: string, eventProperties?: EventProperties): void;
resetIdentity(): void;
addUserProperties(properties: AnalyticsUserOptions): void;
addEventProperties(properties: AnalyticsEventOptions): void;