diff --git a/ui/app/contexts/metametrics.js b/ui/app/contexts/metametrics.js new file mode 100644 index 000000000..0e811585d --- /dev/null +++ b/ui/app/contexts/metametrics.js @@ -0,0 +1,131 @@ +import React, { Component, createContext, useEffect, useCallback, useState } from 'react' +import { useSelector } from 'react-redux' +import PropTypes from 'prop-types' +import { useHistory } from 'react-router-dom' +import { captureException } from '@sentry/browser' + +import { + getCurrentNetworkId, + getSelectedAsset, + getAccountType, + getNumberOfAccounts, + getNumberOfTokens, +} from '../selectors/selectors' +import { + txDataSelector, +} from '../selectors/confirm-transaction' +import { getEnvironmentType } from '../../../app/scripts/lib/util' +import { + sendMetaMetricsEvent, + sendCountIsTrackable, +} from '../helpers/utils/metametrics.util' + + +export const MetaMetricsContext = createContext(() => { + captureException( + Error(`MetaMetrics context function was called from a react node that is not a descendant of a MetaMetrics context provider`) + ) +}) + +export function MetaMetricsProvider ({ children }) { + const txData = useSelector(txDataSelector) || {} + const network = useSelector(getCurrentNetworkId) + const environmentType = getEnvironmentType() + const activeCurrency = useSelector(getSelectedAsset) + const accountType = useSelector(getAccountType) + const confirmTransactionOrigin = txData.origin + const metaMetricsId = useSelector((state) => state.metamask.metaMetricsId) + const participateInMetaMetrics = useSelector((state) => state.metamask.participateInMetaMetrics) + const metaMetricsSendCount = useSelector((state) => state.metamask.metaMetricsSendCount) + const numberOfTokens = useSelector(getNumberOfTokens) + const numberOfAccounts = useSelector(getNumberOfAccounts) + const history = useHistory() + const [state, setState] = useState(() => ({ + currentPath: window.location.href, + previousPath: '', + })) + + const { previousPath, currentPath } = state + + useEffect(() => { + const unlisten = history.listen(() => setState((prevState) => ({ + currentPath: window.location.href, + previousPath: prevState.currentPath, + }))) + // remove this listener if the component is no longer mounted + return unlisten + }, [history]) + + const metricsEvent = useCallback((config = {}, overrides = {}) => { + const { eventOpts = {} } = config + const { name = '' } = eventOpts + const { pathname: overRidePathName = '' } = overrides + const isSendFlow = Boolean(name.match(/^send|^confirm/) || overRidePathName.match(/send|confirm/)) + + if (participateInMetaMetrics || config.isOptIn) { + return sendMetaMetricsEvent({ + network, + environmentType, + activeCurrency, + accountType, + confirmTransactionOrigin, + metaMetricsId, + numberOfTokens, + numberOfAccounts, + version: global.platform.getVersion(), + ...config, + previousPath, + currentPath, + excludeMetaMetricsId: isSendFlow && !sendCountIsTrackable(metaMetricsSendCount + 1), + ...overrides, + }) + } + }, [ + network, + environmentType, + activeCurrency, + accountType, + confirmTransactionOrigin, + participateInMetaMetrics, + previousPath, + metaMetricsId, + numberOfTokens, + numberOfAccounts, + currentPath, + metaMetricsSendCount, + ]) + + return ( + + {children} + + ) +} + +MetaMetricsProvider.propTypes = { children: PropTypes.node } + +export class LegacyMetaMetricsProvider extends Component { + static propTypes = { + children: PropTypes.node, + } + + static defaultProps = { + children: undefined, + } + + static contextType = MetaMetricsContext + + static childContextTypes = { + metricsEvent: PropTypes.func, + } + + getChildContext () { + return { + metricsEvent: this.context, + } + } + + render () { + return this.props.children + } +} diff --git a/ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js b/ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js deleted file mode 100644 index e67ab9a21..000000000 --- a/ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js +++ /dev/null @@ -1,130 +0,0 @@ -import { Component } from 'react' -import { connect } from 'react-redux' -import PropTypes from 'prop-types' -import { withRouter } from 'react-router-dom' -import { compose } from 'redux' -import { - getCurrentNetworkId, - getSelectedAsset, - getAccountType, - getNumberOfAccounts, - getNumberOfTokens, - txDataSelector, -} from '../../../selectors' - -import { getEnvironmentType } from '../../../../../app/scripts/lib/util' -import { - sendMetaMetricsEvent, - sendCountIsTrackable, -} from '../../utils/metametrics.util' - -class MetaMetricsProvider extends Component { - static propTypes = { - accountType: PropTypes.string.isRequired, - activeCurrency: PropTypes.string.isRequired, - children: PropTypes.object.isRequired, - confirmTransactionOrigin: PropTypes.string, - environmentType: PropTypes.string.isRequired, - history: PropTypes.object.isRequired, - metaMetricsId: PropTypes.string, - metaMetricsSendCount: PropTypes.number.isRequired, - network: PropTypes.string.isRequired, - numberOfTokens: PropTypes.number, - numberOfAccounts: PropTypes.number, - participateInMetaMetrics: PropTypes.bool, - version: PropTypes.string, - } - - static childContextTypes = { - metricsEvent: PropTypes.func, - } - - constructor (props) { - super(props) - - props.history.listen(() => { - this.setState((prevState) => ({ - previousPath: prevState.currentPath, - currentPath: window.location.href, - })) - }) - } - - state = { - previousPath: '', - currentPath: window.location.href, - } - - getChildContext () { - const { - network, - environmentType, - activeCurrency, - accountType, - confirmTransactionOrigin, - metaMetricsId, - participateInMetaMetrics, - metaMetricsSendCount, - numberOfTokens, - numberOfAccounts, - version, - } = this.props - const { previousPath, currentPath } = this.state - - return { - metricsEvent: (config = {}, overrides = {}) => { - const { eventOpts = {} } = config - const { name = '' } = eventOpts - const { pathname: overRidePathName = '' } = overrides - const isSendFlow = Boolean(name.match(/^send|^confirm/) || overRidePathName.match(/send|confirm/)) - - if (participateInMetaMetrics || config.isOptIn) { - return sendMetaMetricsEvent({ - network, - environmentType, - activeCurrency, - accountType, - confirmTransactionOrigin, - metaMetricsId, - numberOfTokens, - numberOfAccounts, - version, - ...config, - previousPath, - currentPath, - excludeMetaMetricsId: isSendFlow && !sendCountIsTrackable(metaMetricsSendCount + 1), - ...overrides, - }) - } - }, - } - } - - render () { - return this.props.children - } -} - -const mapStateToProps = (state) => { - const txData = txDataSelector(state) || {} - - return { - network: getCurrentNetworkId(state), - environmentType: getEnvironmentType(), - activeCurrency: getSelectedAsset(state), - accountType: getAccountType(state), - confirmTransactionOrigin: txData.origin, - metaMetricsId: state.metamask.metaMetricsId, - participateInMetaMetrics: state.metamask.participateInMetaMetrics, - metaMetricsSendCount: state.metamask.metaMetricsSendCount, - numberOfTokens: getNumberOfTokens(state), - numberOfAccounts: getNumberOfAccounts(state), - version: global.platform.getVersion(), - } -} - -export default compose( - withRouter, - connect(mapStateToProps) -)(MetaMetricsProvider) - diff --git a/ui/app/hooks/useMetricEvent.js b/ui/app/hooks/useMetricEvent.js new file mode 100644 index 000000000..33ca751ec --- /dev/null +++ b/ui/app/hooks/useMetricEvent.js @@ -0,0 +1,9 @@ +import { useContext, useCallback } from 'react' +import { MetaMetricsContext } from '../contexts/metametrics' + + +export function useMetricEvent (config = {}, overrides = {}) { + const metricsEvent = useContext(MetaMetricsContext) + const trackEvent = useCallback(() => metricsEvent(config, overrides), [config, overrides]) + return trackEvent +} diff --git a/ui/app/pages/index.js b/ui/app/pages/index.js index 3d8ccf165..5ec61b4d2 100644 --- a/ui/app/pages/index.js +++ b/ui/app/pages/index.js @@ -6,7 +6,7 @@ import * as Sentry from '@sentry/browser' import ErrorPage from './error' import Routes from './routes' import { I18nProvider, LegacyI18nProvider } from '../contexts/i18n' -import MetaMetricsProvider from '../helpers/higher-order-components/metametrics/metametrics.provider' +import { MetaMetricsProvider, LegacyMetaMetricsProvider } from '../contexts/metametrics' class Index extends PureComponent { state = {} @@ -42,11 +42,13 @@ class Index extends PureComponent { - - - - - + + + + + + +