1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-24 02:58:09 +01:00
metamask-extension/ui/app/contexts/metametrics.new.js
2020-12-02 15:41:30 -06:00

210 lines
5.8 KiB
JavaScript

/**
* This file is intended to be renamed to metametrics.js once the conversion is complete.
* MetaMetrics is our own brand, and should remain aptly named regardless of the underlying
* metrics system. This file implements Segment analytics tracking.
*/
import React, {
Component,
createContext,
useEffect,
useRef,
useCallback,
} from 'react'
import { useSelector } from 'react-redux'
import PropTypes from 'prop-types'
import { matchPath, useLocation, useRouteMatch } from 'react-router-dom'
import { captureException, captureMessage } from '@sentry/browser'
import { omit } from 'lodash'
import { getEnvironmentType } from '../../../app/scripts/lib/util'
import { PATH_NAME_MAP } from '../helpers/constants/routes'
import { txDataSelector } from '../selectors'
import { trackMetaMetricsEvent, trackMetaMetricsPage } from '../store/actions'
// type imports
/**
* @typedef {import('../../../shared/constants/metametrics').MetaMetricsEventPayload} MetaMetricsEventPayload
* @typedef {import('../../../shared/constants/metametrics').MetaMetricsEventOptions} MetaMetricsEventOptions
* @typedef {import('../../../shared/constants/metametrics').MetaMetricsPageObject} MetaMetricsPageObject
* @typedef {import('../../../shared/constants/metametrics').MetaMetricsReferrerObject} MetaMetricsReferrerObject
*/
// types
/**
* @typedef {Omit<MetaMetricsEventPayload, 'environmentType' | 'page' | 'referrer'>} UIMetricsEventPayload
*/
/**
* @typedef {(
* payload: UIMetricsEventPayload,
* options: MetaMetricsEventOptions
* ) => Promise<void>} UITrackEventMethod
*/
/**
* @type {React.Context<UITrackEventMethod>}
*/
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`,
),
)
})
const PATHS_TO_CHECK = Object.keys(PATH_NAME_MAP)
/**
* Returns the current page if it matches out route map, as well as the origin
* if there is a confirmation that was triggered by a dapp
* @returns {{
* page?: MetaMetricsPageObject
* referrer?: MetaMetricsReferrerObject
* }}
*/
function useSegmentContext() {
const match = useRouteMatch({
path: PATHS_TO_CHECK,
exact: true,
strict: true,
})
const txData = useSelector(txDataSelector) || {}
const confirmTransactionOrigin = txData.origin
const referrer = confirmTransactionOrigin
? {
url: confirmTransactionOrigin,
}
: undefined
const page = match
? {
path: match.path,
title: PATH_NAME_MAP[match.path],
url: match.path,
}
: undefined
return {
page,
referrer,
}
}
export function MetaMetricsProvider({ children }) {
const location = useLocation()
const context = useSegmentContext()
/**
* @type {UITrackEventMethod}
*/
const trackEvent = useCallback(
(payload, options) => {
trackMetaMetricsEvent(
{
...payload,
environmentType: getEnvironmentType(),
...context,
},
options,
)
},
[context],
)
// Used to prevent double tracking page calls
const previousMatch = useRef()
/**
* Anytime the location changes, track a page change with segment.
* Previously we would manually track changes to history and keep a
* reference to the previous url, but with page tracking we can see
* which page the user is on and their navigation path.
*/
useEffect(() => {
const environmentType = getEnvironmentType()
const match = matchPath(location.pathname, {
path: PATHS_TO_CHECK,
exact: true,
strict: true,
})
// Start by checking for a missing match route. If this falls through to
// the else if, then we know we have a matched route for tracking.
if (!match) {
captureMessage(`Segment page tracking found unmatched route`, {
extra: {
previousMatch,
currentPath: location.pathname,
},
})
} else if (
previousMatch.current !== match.path &&
!(
environmentType === 'notification' &&
match.path === '/' &&
previousMatch.current === undefined
)
) {
// When a notification window is open by a Dapp we do not want to track
// the initial home route load that can sometimes happen. To handle
// this we keep track of the previousMatch, and we skip the event track
// in the event that we are dealing with the initial load of the
// homepage
const { path, params } = match
const name = PATH_NAME_MAP[path]
trackMetaMetricsPage(
{
name,
// We do not want to send addresses or accounts in any events
// Some routes include these as params.
params: omit(params, ['account', 'address']),
environmentType,
page: context.page,
referrer: context.referrer,
},
{
isOptInPath: location.pathname.startsWith('/initialize'),
},
)
}
previousMatch.current = match?.path
}, [location, context])
return (
<MetaMetricsContext.Provider value={trackEvent}>
{children}
</MetaMetricsContext.Provider>
)
}
MetaMetricsProvider.propTypes = { children: PropTypes.node }
export class LegacyMetaMetricsProvider extends Component {
static propTypes = {
children: PropTypes.node,
}
static defaultProps = {
children: undefined,
}
static contextType = MetaMetricsContext
static childContextTypes = {
// This has to be different than the type name for the old metametrics file
// using the same name would result in whichever was lower in the tree to be
// used.
trackEvent: PropTypes.func,
}
getChildContext() {
return {
trackEvent: this.context,
}
}
render() {
return this.props.children
}
}