1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-18 07:23:21 +01:00
metamask-extension/ui/app/contexts/metametrics.new.js
Erik Marks 76a2a9bb8b
@metamask/eslint config@5.0.0 (#10358)
* @metamask/eslint-config@5.0.0
* Update eslintrc and prettierrc
* yarn lint:fix
2021-02-04 10:15:23 -08: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;
}
}