mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 01:39:44 +01:00
210 lines
5.8 KiB
JavaScript
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;
|
|
}
|
|
}
|